diff --git a/constants/role.constant.js b/constants/role.constant.js index 3915e170..eefdcb8d 100644 --- a/constants/role.constant.js +++ b/constants/role.constant.js @@ -42,7 +42,8 @@ const hackerRole = { Constants.Routes.teamRoutes.join, Constants.Routes.teamRoutes.post, - Constants.Routes.teamRoutes.get + Constants.Routes.teamRoutes.get, + Constants.Routes.teamRoutes.leave ] }; diff --git a/constants/routes.constant.js b/constants/routes.constant.js index b4963104..07e2aa23 100644 --- a/constants/routes.constant.js +++ b/constants/routes.constant.js @@ -146,6 +146,10 @@ const teamRoutes = { requestType: Constants.REQUEST_TYPES.PATCH, uri: "/api/team/join/", }, + "leave": { + requestType: Constants.REQUEST_TYPES.PATCH, + uri: "/api/team/leave/", + }, }; const volunteerRoutes = { diff --git a/constants/success.constant.js b/constants/success.constant.js index e7d5ae2d..7e27bc91 100644 --- a/constants/success.constant.js +++ b/constants/success.constant.js @@ -38,6 +38,7 @@ const TEAM_GET_BY_ID = "Team found by id."; const TEAM_CREATE = "Team creation successful."; const TEAM_JOIN = "Team join successful."; const TEAM_READ = "Team retrieval successful."; +const TEAM_HACKER_LEAVE = "Removal from team successful."; const VOLUNTEER_CREATE = "Volunteer creation successful."; @@ -79,6 +80,7 @@ module.exports = { TEAM_CREATE: TEAM_CREATE, TEAM_JOIN: TEAM_JOIN, TEAM_READ: TEAM_READ, + TEAM_HACKER_LEAVE: TEAM_HACKER_LEAVE, VOLUNTEER_CREATE: VOLUNTEER_CREATE, }; \ No newline at end of file diff --git a/controllers/team.controller.js b/controllers/team.controller.js index d84d9acd..ed001a82 100644 --- a/controllers/team.controller.js +++ b/controllers/team.controller.js @@ -71,8 +71,24 @@ function createdTeam(req, res) { }); } +/** + * @function leftTeam + * @param {*} req + * @param {*} res + * @return {JSON} Success status + * @description return success message of removing self from team. + */ + +function leftTeam(req, res) { + return res.status(200).json({ + message: Constants.Success.TEAM_HACKER_LEAVE, + data: {}, + }); +} + module.exports = { joinedTeam: joinedTeam, createdTeam: createdTeam, showTeam: showTeam, + leftTeam: leftTeam, }; \ No newline at end of file diff --git a/docs/api/api_data.js b/docs/api/api_data.js index beef9127..b5d89693 100644 --- a/docs/api/api_data.js +++ b/docs/api/api_data.js @@ -2261,6 +2261,43 @@ define({ "url": "https://api.mchacks.ca/api/team/" }] }, + { + "type": "patch", + "url": "/team/leave/", + "title": "Allows a logged in hacker to leave current team", + "name": "deleteSelfFromTeam", + "group": "Team", + "version": "1.1.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": "{}
" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Removal from team successful.\", \n \"data\": {}\n}", + "type": "object" + }] + }, + "filename": "routes/api/team.js", + "groupTitle": "Team", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/team/leave/" + }] + }, { "type": "get", "url": "/team/:id", @@ -2375,9 +2412,6 @@ define({ "type": "object" }] }, - "permission": [{ - "name": "Administrator" - }], "filename": "routes/api/team.js", "groupTitle": "Team", "sampleRequest": [{ diff --git a/docs/api/api_data.json b/docs/api/api_data.json index 82c5e35e..e8b957cf 100644 --- a/docs/api/api_data.json +++ b/docs/api/api_data.json @@ -2260,6 +2260,43 @@ "url": "https://api.mchacks.ca/api/team/" }] }, + { + "type": "patch", + "url": "/team/leave/", + "title": "Allows a logged in hacker to leave current team", + "name": "deleteSelfFromTeam", + "group": "Team", + "version": "1.1.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": "{}
" + } + ] + }, + "examples": [{ + "title": "Success-Response: ", + "content": "{\n \"message\": \"Removal from team successful.\", \n \"data\": {}\n}", + "type": "object" + }] + }, + "filename": "routes/api/team.js", + "groupTitle": "Team", + "sampleRequest": [{ + "url": "https://api.mchacks.ca/api/team/leave/" + }] + }, { "type": "get", "url": "/team/:id", @@ -2374,9 +2411,6 @@ "type": "object" }] }, - "permission": [{ - "name": "Administrator" - }], "filename": "routes/api/team.js", "groupTitle": "Team", "sampleRequest": [{ diff --git a/docs/api/api_project.js b/docs/api/api_project.js index 45ecedd3..22cdc45c 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-07T03:26:47.443Z", - "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-08T22:07:07.661Z", + "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 5aff4cd2..543c38d6 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-07T03:26:47.443Z", - "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-08T22:07:07.661Z", + "url": "http://apidocjs.com", + "version": "0.17.7" + } } \ No newline at end of file diff --git a/middlewares/team.middleware.js b/middlewares/team.middleware.js index 611e06e0..41386cfe 100644 --- a/middlewares/team.middleware.js +++ b/middlewares/team.middleware.js @@ -165,6 +165,34 @@ async function findById(req, res, next) { return next(); } +/** + * @async + * @function deleteUserFromTeam + * @param {{user: {id: ObjectId}} req + * @param {*} res + * @return {JSON} Success or error status + * @description Removes the hacker associated with req.user.id from the team under teamId. If hacker is not part of a team, it does nothing. + */ +async function deleteUserFromTeam(req, res, next) { + const hacker = await Services.Hacker.findByAccountId(req.user.id); + + if (!hacker) { + return next({ + status: 404, + message: Constants.Error.HACKER_404_MESSAGE, + data: { + id: req.user.id + } + }); + } + const oldTeamId = hacker.teamId; + if (oldTeamId) { + await Services.Team.removeMember(oldTeamId, hacker._id); + await Services.Team.removeTeamIfEmpty(oldTeamId); + } + next(); +} + /** * @async * @function updateHackerTeam @@ -343,7 +371,7 @@ async function parseNewTeam(req, res, next) { } // hacker should not be in another team - if (hacker.teamId !== undefined) { + if (hacker.teamId !== undefined && hacker.teamId !== null) { return next({ status: 409, message: Constants.Error.TEAM_MEMBER_409_MESSAGE, @@ -366,4 +394,5 @@ module.exports = { parseNewTeam: Util.asyncMiddleware(parseNewTeam), ensureFreeTeamName: Util.asyncMiddleware(ensureFreeTeamName), populateMemberAccountsById: Util.asyncMiddleware(populateMemberAccountsById), + deleteUserFromTeam: Util.asyncMiddleware(deleteUserFromTeam), }; \ No newline at end of file diff --git a/routes/api/team.js b/routes/api/team.js index 03ad14e5..27dd46a9 100644 --- a/routes/api/team.js +++ b/routes/api/team.js @@ -72,7 +72,6 @@ module.exports = { * "message": "Team join successful.", * "data": {} * } - * @apiPermission Administrator */ teamRouter.route("/join/").patch( Middleware.Auth.ensureAuthenticated(), @@ -87,6 +86,27 @@ module.exports = { Controllers.Team.joinedTeam ); + /** + * @api {patch} /team/leave/ Allows a logged in hacker to leave current team + * @apiName deleteSelfFromTeam + * @apiGroup Team + * @apiVersion 1.1.1 + * + * @apiSuccess {string} message Success message + * @apiSuccess {object} data {} + * @apiSuccessExample {object} Success-Response: + * { + * "message": "Removal from team successful.", + * "data": {} + * } + */ + teamRouter.route("/leave").patch( + Middleware.Auth.ensureAuthenticated(), + Middleware.Auth.ensureAuthorized(), + Middleware.Team.deleteUserFromTeam, + Controllers.Team.leftTeam + ); + /** * @api {get} /team/:id get a team's information * @apiName getTeam diff --git a/services/team.service.js b/services/team.service.js index 274a41c2..d79c5112 100644 --- a/services/team.service.js +++ b/services/team.service.js @@ -87,11 +87,11 @@ async function removeMember(teamId, hackerId) { return null; } - return Team.update({ + return Team.findOneAndUpdate({ _id: teamId }, { $pull: { - members: [hackerId] + members: hackerId } }); } diff --git a/tests/team.test.js b/tests/team.test.js index 4e2d590d..cd9358ce 100644 --- a/tests/team.test.js +++ b/tests/team.test.js @@ -358,4 +358,38 @@ describe("PATCH change team", function () { }); }); }); + + it("should SUCCEED and leave a team.", function (done) { + util.auth.login(agent, util.account.Account1, (error) => { + if (error) { + agent.close(); + return done(error); + } + return agent + .patch(`/api/team/leave/`) + .type("application/json") + .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.TEAM_HACKER_LEAVE); + res.body.should.have.property("data"); + done(); + }); + }); + }); + it("should FAIL to leave a team due to invalid authentication.", function (done) { + chai.request(server.app) + .patch(`/api/team/leave/`) + .type("application/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); + res.body.should.have.property("data"); + + done(); + }); + }); }); \ No newline at end of file