Skip to content

Commit 9083e77

Browse files
authored
Add invite route (#252)
* Add invite route * Documentation
1 parent 5820cb3 commit 9083e77

10 files changed

Lines changed: 207 additions & 46 deletions

File tree

constants/routes.constant.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ const authRoutes = {
1010
requestType: Constants.REQUEST_TYPES.POST,
1111
uri: "/api/auth/logout"
1212
},
13-
"invite": {
14-
requestType: Constants.REQUEST_TYPES.POST,
15-
uri: "/api/auth/invite"
16-
},
1713
"getSelfRoleBindindings": {
1814
requestType: Constants.REQUEST_TYPES.GET,
1915
uri: "/api/auth/rolebindings/" + Constants.ROLE_CATEGORIES.SELF
@@ -181,7 +177,16 @@ const staffRoutes = {
181177
"hackerStats": {
182178
requestType: Constants.REQUEST_TYPES.GET,
183179
uri: "/api/hacker/stats",
184-
}
180+
},
181+
"postInvite": {
182+
requestType: Constants.REQUEST_TYPES.POST,
183+
uri: "/api/account/invite"
184+
},
185+
"getInvite": {
186+
requestType: Constants.REQUEST_TYPES.GET,
187+
uri: "/api/account/invite"
188+
},
189+
185190
}
186191

187192
const allRoutes = {

constants/success.constant.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const ACCOUNT_READ = "Account retrieval successful.";
66
const ACCOUNT_CREATE = "Account creation successful.";
77
const ACCOUNT_UPDATE = "Account update successful.";
88
const ACCOUNT_INVITE = "Account invitation successful.";
9+
const ACCOUNT_GET_INVITES = "Invite retrieval successful.";
910

1011
const AUTH_LOGIN = "Login successful.";
1112
const AUTH_LOGOUT = "Logout successful.";
@@ -46,8 +47,8 @@ module.exports = {
4647
ACCOUNT_CREATE: ACCOUNT_CREATE,
4748
ACCOUNT_UPDATE: ACCOUNT_UPDATE,
4849
ACCOUNT_INVITE: ACCOUNT_INVITE,
50+
ACCOUNT_GET_INVITES: ACCOUNT_GET_INVITES,
4951
ACCOUNT_READ: ACCOUNT_READ,
50-
5152
AUTH_LOGIN: AUTH_LOGIN,
5253
AUTH_LOGOUT: AUTH_LOGOUT,
5354
AUTH_SEND_RESET_EMAIL: AUTH_SEND_RESET_EMAIL,

controllers/account.controller.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,18 @@ function invitedAccount(req, res) {
6363
});
6464
}
6565

66+
function gotInvites(req, res) {
67+
return res.status(200).json({
68+
message: Constants.Success.ACCOUNT_GET_INVITES,
69+
data: {
70+
invites: req.body.invites
71+
}
72+
});
73+
}
6674

6775
module.exports = {
6876
addUser: addUser,
77+
gotInvites: gotInvites,
6978
updatedAccount: updatedAccount,
7079
invitedAccount: invitedAccount,
7180
showAccount: showAccount,

docs/api/api_data.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,27 @@ define({
205205
"url": "https://api.mchacks.ca/api/account/:id"
206206
}]
207207
},
208+
{
209+
"type": "get",
210+
"url": "/account/invite",
211+
"title": "Get all of the invites.",
212+
"name": "getAllInvites",
213+
"group": "Account",
214+
"version": "0.0.8",
215+
"description": "<p>Get all of the invites that currently exist in the database.</p>",
216+
"success": {
217+
"examples": [{
218+
"title": "Success-Response: ",
219+
"content": "{\n \"message\": \"Invite retrieval successful.\", \n \"data\": [{\n \"email\":\"abc@def.com\",\n \"accountType\":\"Hacker\"\n }]\n }",
220+
"type": "object"
221+
}]
222+
},
223+
"filename": "routes/api/account.js",
224+
"groupTitle": "Account",
225+
"sampleRequest": [{
226+
"url": "https://api.mchacks.ca/api/account/invite"
227+
}]
228+
},
208229
{
209230
"type": "post",
210231
"url": "/account/invite",

docs/api/api_data.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,27 @@
204204
"url": "https://api.mchacks.ca/api/account/:id"
205205
}]
206206
},
207+
{
208+
"type": "get",
209+
"url": "/account/invite",
210+
"title": "Get all of the invites.",
211+
"name": "getAllInvites",
212+
"group": "Account",
213+
"version": "0.0.8",
214+
"description": "<p>Get all of the invites that currently exist in the database.</p>",
215+
"success": {
216+
"examples": [{
217+
"title": "Success-Response: ",
218+
"content": "{\n \"message\": \"Invite retrieval successful.\", \n \"data\": [{\n \"email\":\"abc@def.com\",\n \"accountType\":\"Hacker\"\n }]\n }",
219+
"type": "object"
220+
}]
221+
},
222+
"filename": "routes/api/account.js",
223+
"groupTitle": "Account",
224+
"sampleRequest": [{
225+
"url": "https://api.mchacks.ca/api/account/invite"
226+
}]
227+
},
207228
{
208229
"type": "post",
209230
"url": "/account/invite",

middlewares/account.middleware.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,12 +202,24 @@ async function inviteAccount(req, res, next) {
202202
message: Constants.Error.EMAIL_500_MESSAGE,
203203
});
204204
}
205-
205+
}
206+
/**
207+
* Gets all of the invites in the database and adds it to req.body.
208+
* @param {{}} req
209+
* @param {*} res
210+
* @param {(err?)=>void} next
211+
*/
212+
async function getInvites(req, res, next) {
213+
const invites = await Services.AccountConfirmation.find({});
214+
req.body.invites = invites;
215+
next();
206216
}
207217

208218
module.exports = {
209219
parsePatch: parsePatch,
210220
parseAccount: parseAccount,
221+
// untested
222+
getInvites: Middleware.Util.asyncMiddleware(getInvites),
211223
getByEmail: Middleware.Util.asyncMiddleware(getByEmail),
212224
getById: Middleware.Util.asyncMiddleware(getById),
213225
// untested

routes/api/account.js

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,64 @@ module.exports = {
141141
Controllers.Account.addUser
142142
);
143143

144+
/**
145+
* @api {post} /account/invite invites a user to create an account with the specified accountType
146+
* @apiName inviteAccount
147+
* @apiGroup Account
148+
* @apiVersion 0.0.8
149+
* @apiDescription sends link with token to be used with the account/create route
150+
*
151+
* @apiParam (body) {String} [email] email of the account to be created and where to send the link
152+
* @apiParam (body) {String} [accountType] the type of the account which the user can create, for sponsor this should specify tier as well
153+
*
154+
* @apiSuccess {string} message Success message
155+
* @apiSuccess {object} data Account object
156+
* @apiSuccessExample {object} Success-Response:
157+
* {
158+
"message": "Successfully invited user",
159+
"data": {}
160+
}
161+
* @apiError {string} message Error message
162+
* @apiError {object} data Error object
163+
* @apiErrorExample {object} Error-Response:
164+
* {
165+
"message": "Invalid Authentication",
166+
"data": {
167+
"route": "/invite"
168+
}
169+
}
170+
*/
171+
accountRouter.route("/invite").post(
172+
Middleware.Auth.ensureAuthenticated(),
173+
Middleware.Auth.ensureAuthorized(),
174+
Middleware.Validator.Account.inviteAccountValidator,
175+
Middleware.parseBody.middleware,
176+
Middleware.Account.inviteAccount,
177+
Controllers.Account.invitedAccount
178+
);
179+
/**
180+
* @api {get} /account/invite Get all of the invites.
181+
* @apiName getAllInvites
182+
* @apiGroup Account
183+
* @apiVersion 0.0.8
184+
* @apiDescription Get all of the invites that currently exist in the database.
185+
* @apiSuccessExample {object} Success-Response:
186+
* {
187+
"message": "Invite retrieval successful.",
188+
"data": [{
189+
"email":"abc@def.com",
190+
"accountType":"Hacker"
191+
}]
192+
}
193+
*/
194+
accountRouter.route("/invite").get(
195+
Middleware.Auth.ensureAuthenticated(),
196+
Middleware.Auth.ensureAuthorized(),
197+
Middleware.parseBody.middleware,
198+
Middleware.Account.getInvites,
199+
Controllers.Account.gotInvites
200+
);
201+
144202
/**
145203
* @api {patch} /account/:id update an account's information
146204
* @apiName updateOneUser
@@ -241,42 +299,6 @@ module.exports = {
241299
Controllers.Account.showAccount
242300
);
243301

244-
/**
245-
* @api {post} /account/invite invites a user to create an account with the specified accountType
246-
* @apiName inviteAccount
247-
* @apiGroup Account
248-
* @apiVersion 0.0.8
249-
* @apiDescription sends link with token to be used with the account/create route
250-
*
251-
* @apiParam (body) {String} [email] email of the account to be created and where to send the link
252-
* @apiParam (body) {String} [accountType] the type of the account which the user can create, for sponsor this should specify tier as well
253-
*
254-
* @apiSuccess {string} message Success message
255-
* @apiSuccess {object} data Account object
256-
* @apiSuccessExample {object} Success-Response:
257-
* {
258-
"message": "Successfully invited user",
259-
"data": {}
260-
}
261-
* @apiError {string} message Error message
262-
* @apiError {object} data Error object
263-
* @apiErrorExample {object} Error-Response:
264-
* {
265-
"message": "Invalid Authentication",
266-
"data": {
267-
"route": "/invite"
268-
}
269-
}
270-
*/
271-
accountRouter.route("/invite").post(
272-
Middleware.Auth.ensureAuthenticated(),
273-
Middleware.Auth.ensureAuthorized(),
274-
Middleware.Validator.Account.inviteAccountValidator,
275-
Middleware.parseBody.middleware,
276-
Middleware.Account.inviteAccount,
277-
Controllers.Account.invitedAccount
278-
);
279-
280302
apiRouter.use("/account", accountRouter);
281303
}
282304
};

services/accountConfirmation.service.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ function findById(id) {
3232
return AccountConfirmation.findById(id, logger.queryCallbackFactory(TAG, "AccountConfirmation", "mongoId"));
3333
}
3434

35+
/**
36+
* @function find
37+
* @param {*} query the query to search the database by.
38+
* @return {DocumentQuery<any[]>} The document query will resolve to either account confirmations or null.
39+
* @description Finds an account by query.
40+
*/
41+
function find(query) {
42+
const TAG = `[ AccountConfirmation Service # find ]`;
43+
return AccountConfirmation.find(query, logger.queryCallbackFactory(TAG, "AccountConfirmation", query));
44+
}
45+
3546
/**
3647
* Creates Account Confirmation document in the database
3748
* @param {String} type the type of user which to create the token for
@@ -158,6 +169,7 @@ function generateAccountInvitationEmail(address, receiverEmail, type, token) {
158169
return mailData;
159170
}
160171
module.exports = {
172+
find: find,
161173
findById: findById,
162174
findByAccountId: findByAccountId,
163175
create: create,

tests/account.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,4 +544,55 @@ describe("POST invite account", function () {
544544
});
545545
});
546546
});
547+
});
548+
549+
describe("GET invites", function () {
550+
it("Should FAIL to get all invites due to Authentication", function (done) {
551+
chai.request(server.app)
552+
.get("/api/account/invite")
553+
.end(function (err, res) {
554+
res.should.have.status(401);
555+
res.body.should.have.property("message");
556+
res.body.message.should.equal(Constants.Error.AUTH_401_MESSAGE);
557+
done();
558+
});
559+
});
560+
it("Should FAIL to get all invites due to Authorization", function (done) {
561+
util.auth.login(agent, storedAccount1, (error) => {
562+
if (error) {
563+
agent.close();
564+
return done(error);
565+
}
566+
return agent
567+
.get("/api/account/invite")
568+
.end(function (err, res) {
569+
res.should.have.status(403);
570+
res.body.should.have.property("message");
571+
res.body.message.should.equal(Constants.Error.AUTH_403_MESSAGE);
572+
done();
573+
});
574+
});
575+
});
576+
it("Should SUCCEED to get all invites", function (done) {
577+
util.auth.login(agent, Admin1, (error) => {
578+
if (error) {
579+
agent.close();
580+
return done(error);
581+
}
582+
return agent
583+
.get("/api/account/invite")
584+
.end(function (err, res) {
585+
if (err) {
586+
return done(err);
587+
}
588+
res.should.have.status(200);
589+
res.body.should.have.property("message");
590+
res.body.message.should.equal(Constants.Success.ACCOUNT_GET_INVITES);
591+
res.body.should.have.property("data");
592+
res.body.data.should.have.property("invites");
593+
res.body.data.invites.length.should.equal(util.accountConfirmation.AccountConfirmationTokens.length);
594+
done();
595+
});
596+
});
597+
});
547598
});

tests/util/accountConfirmation.test.util.js

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,17 @@ const HackerConfirmation2 = {
2626
"accountId": Util.Account.NonConfirmedAccount2._id,
2727
"accountType": Constants.HACKER,
2828
"email": Util.Account.NonConfirmedAccount2.email
29-
}
29+
};
3030

3131
const HackerConfirmation3 = {
3232
"_id": mongoose.Types.ObjectId(),
33-
"email": Util.Account.newAccount1
33+
"email": Util.Account.newAccount1.email
34+
};
35+
36+
const HackerConfirmation4 = {
37+
"_id": mongoose.Types.ObjectId(),
38+
"accountType": Constants.HACKER,
39+
"email": "abcd@efgh.com"
3440
};
3541

3642
// Using a real ID which is stored but corresponds to another account
@@ -46,7 +52,8 @@ const FakeToken = Services.AccountConfirmation.generateToken(FakeHackerToken._id
4652
const AccountConfirmationTokens = [
4753
HackerConfirmation,
4854
HackerConfirmation2,
49-
HackerConfirmation3
55+
HackerConfirmation3,
56+
HackerConfirmation4
5057
];
5158

5259
function storeAll(attributes) {

0 commit comments

Comments
 (0)