Skip to content

Commit d580d86

Browse files
authored
Merge pull request #28 from andela/ft-user-like/unlike-accommodation-168428851
#168428851 Implements Accommodation Like and Unlike functionality
2 parents 0e2df44 + 827b069 commit d580d86

File tree

9 files changed

+336
-42
lines changed

9 files changed

+336
-42
lines changed

src/controllers/accommodationController.js

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import models from '../models';
22
import { successResponse, errorResponse } from '../utils';
33

4-
const { Accommodations, Rooms } = models;
4+
const { Accommodations, Rooms, Reaction } = models;
55

66
const association = [
77
{
@@ -11,6 +11,33 @@ const association = [
1111
}
1212
];
1313

14+
const reactToAccommodation = async (req, res, reactionType) => {
15+
const { accommodationId } = req.params;
16+
const { userId } = req.user;
17+
try {
18+
const result = await Reaction.findOne({
19+
where: { accommodationId, userId }
20+
});
21+
if (!result) {
22+
await Reaction.create({ reaction: reactionType, userId, accommodationId });
23+
} else if (result.reaction === reactionType) {
24+
await Reaction.destroy({
25+
where: { accommodationId, userId }
26+
});
27+
} else {
28+
await Reaction.update({ reaction: reactionType },
29+
{ where: { accommodationId, userId } });
30+
}
31+
const { count } = await Reaction.findAndCountAll({
32+
where: { accommodationId, reaction: reactionType }
33+
});
34+
reactionType ? reactionType = 'like' : reactionType = 'unlike';
35+
return successResponse(res, 200, `Total number of ${reactionType}: ${count}`, { [reactionType]: count });
36+
} catch (error) {
37+
return errorResponse(res, 500, 'Internal server error');
38+
}
39+
};
40+
1441
/**
1542
* @class AccommodationController
1643
* @description Controllers for handling accommodation requests
@@ -97,6 +124,28 @@ class AccommodationController {
97124
}
98125
}
99126

127+
/**
128+
* @method likeAccommodation
129+
* @description Method to like accommodation
130+
* @param {object} req - The Request Object
131+
* @param {object} res - The Response Object
132+
* @returns {object} the response object
133+
*/
134+
static async likeAccommodation(req, res) {
135+
await reactToAccommodation(req, res, true);
136+
}
137+
138+
/**
139+
* @method dislikeAccommodation
140+
* @description Method to unlike accommodation
141+
* @param {object} req - The Request Object
142+
* @param {object} res - The Response Object
143+
* @returns {object} the response object
144+
*/
145+
static async dislikeAccommodation(req, res) {
146+
await reactToAccommodation(req, res, false);
147+
}
148+
100149
/**
101150
* @method deleteAccommodations
102151
* @description Method to delete accommodations

src/controllers/resetPassword.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
hashPassword, successResponse,
44
errorResponse, Jwt, getCallbackUrls
55
} from '../utils';
6-
import sendEmail from '../services';
6+
import * as services from '../services';
77

88
const { baseUrl } = getCallbackUrls;
99

@@ -37,7 +37,7 @@ export default class ResetPasswordController {
3737
const secret = `${passwordHash}-${createdAt}`;
3838
const token = await Jwt.generateToken({ userId }, secret);
3939
const url = `${baseUrl}/resetpassword/${userId}?token=${token}`;
40-
await sendEmail(email, 'passwordRecovery', { firstName, url });
40+
await services.sendEmail(email, 'passwordRecovery', { firstName, url });
4141
return res.status(200).json(url);
4242
} catch (error) {
4343
return errorResponse(res, 500, error.message);

src/controllers/users.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import models from '../models';
2-
import sendEmail from '../services';
2+
import * as services from '../services';
33
import {
44
status, messages, hashPassword, generateToken,
55
successResponse, errorResponse, conflictResponse, Jwt, bcrypt, getCallbackUrls
@@ -31,12 +31,11 @@ export default class UsersController {
3131
req.body.password = await hashPassword(req.body.password);
3232
const user = await models.Users.create(req.body);
3333
const response = user.toJSON();
34-
3534
delete response.password;
3635
const { id: userId, firstName } = user;
3736
const token = await generateToken({ userId });
3837
const url = `${baseUrl}/users/confirmAccount?token=${token}`;
39-
await sendEmail(email, 'confirmAccount', { firstName, url });
38+
await services.sendEmail(email, 'confirmAccount', { firstName, url });
4039
return successResponse(res, status.created, messages.signUp.success, response, token);
4140
} catch (error) {
4241
return errorResponse(res, status.error, messages.signUp.error);

src/routes/api/accommodation.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ const { validate, Authenticate } = middlewares;
88
const { verifyToken, verifyTravelAdmin } = Authenticate;
99

1010
const {
11-
createAccommodation, getAccommodation, getAccommodationById,
12-
updateAccommodation, deleteAccommodation
11+
createAccommodation, getAccommodation, getAccommodationById, updateAccommodation,
12+
deleteAccommodation, likeAccommodation, dislikeAccommodation
1313
} = AccommodationController;
1414

1515
router.post('/accommodations', verifyToken, verifyTravelAdmin, validate('createAccommodation'), createAccommodation);
1616
router.get('/accommodations', verifyToken, getAccommodation);
1717
router.get('/accommodations/:accommodationId', verifyToken, validate('checkAccommodationId'), getAccommodationById);
1818
router.put('/accommodations/:accommodationId', verifyToken, verifyTravelAdmin, validate('updateAccommodation'), updateAccommodation);
1919
router.delete('/accommodations/:accommodationId', verifyToken, verifyTravelAdmin, validate('checkAccommodationId'), deleteAccommodation);
20+
router.post('/accommodations/:accommodationId/like', verifyToken, validate('checkAccommodationId'), likeAccommodation);
21+
router.post('/accommodations/:accommodationId/unlike', verifyToken, validate('checkAccommodationId'), dislikeAccommodation);
2022

2123
export default router;

src/services/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
import sendEmail from './autoMailer';
22

3-
export default sendEmail;
3+
export { sendEmail };

src/test/controllers/accommodation.test.js

Lines changed: 236 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import app from '../../index';
77
import models from '../../models';
88
import { AccommodationController } from '../../controllers';
99

10-
1110
const accommodationRoute = '/api/v1/accommodations';
1211

1312
const signinRoute = '/api/v1/users/signin';
@@ -419,4 +418,240 @@ describe('Test for Accommodation Endpoints', () => {
419418
});
420419
});
421420
});
421+
422+
describe('Like an Accommodation facility', () => {
423+
it('should successfully like an existing accommodation', async () => {
424+
const user = await request
425+
.post(signinRoute)
426+
.send(login);
427+
const token = `Bearer ${user.body.data.token}`;
428+
429+
const res = await request
430+
.post(`${accommodationRoute}/2b770fbc-76e6-4b5a-afab-882759fd1f06/like`)
431+
.set('Authorization', token);
432+
433+
expect(res).to.have.status(200);
434+
expect(res.body).to.have.property('status');
435+
expect(res.body).to.have.property('message');
436+
});
437+
it('fakes server error when liking an accommodation facility', async () => {
438+
const req = {
439+
params: {
440+
accommodationId: '2b770fbc-76e6-4b5a-afab-882759fd1f06'
441+
},
442+
user: {
443+
userId: 'a4nk945ad'
444+
}
445+
};
446+
const res = {
447+
status: () => { },
448+
json: () => { },
449+
};
450+
sinon.stub(res, 'status').returnsThis();
451+
sinon.stub(models.Reaction, 'findOne').throws();
452+
453+
await AccommodationController.likeAccommodation(req, res);
454+
expect(res.status).to.have.been.calledWith(500);
455+
});
456+
it('fakes a successful like of an accommodation', async () => {
457+
const req = {
458+
params: {
459+
accommodationId: '2b770fbc-76e6-4b5a-afab-882759fd1f06'
460+
},
461+
user: {
462+
userId: 'a4nk945ad'
463+
}
464+
};
465+
const res = {
466+
status: () => { },
467+
json: () => { },
468+
};
469+
const result = {
470+
count: 3
471+
};
472+
sinon.stub(res, 'status').returnsThis();
473+
sinon.stub(models.Reaction, 'findOne').returns(null);
474+
sinon.stub(models.Reaction, 'create').returns(true);
475+
sinon.stub(models.Reaction, 'findAndCountAll').returns(result);
476+
477+
await AccommodationController.likeAccommodation(req, res);
478+
expect(res.status).to.have.been.calledWith(200);
479+
});
480+
it('fakes a successful removal of like from accommodation', async () => {
481+
const req = {
482+
params: {
483+
accommodationId: '2b770fbc-76e6-4b5a-afab-882759fd1f06'
484+
},
485+
user: {
486+
userId: 'a4nk945ad'
487+
}
488+
};
489+
const res = {
490+
status: () => { },
491+
json: () => { },
492+
};
493+
const result = {
494+
reaction: true,
495+
userId: 'a4nk945ad',
496+
accommodationId: 'id789ash7sa'
497+
};
498+
const countResult = {
499+
count: 3
500+
};
501+
sinon.stub(res, 'status').returnsThis();
502+
sinon.stub(models.Reaction, 'findOne').returns(result);
503+
sinon.stub(models.Reaction, 'destroy').returns(true);
504+
sinon.stub(models.Reaction, 'findAndCountAll').returns(countResult);
505+
506+
await AccommodationController.likeAccommodation(req, res);
507+
expect(res.status).to.have.been.calledWith(200);
508+
});
509+
it('fakes a successful liking of accommodation when it was unliked previouly', async () => {
510+
const req = {
511+
params: {
512+
accommodationId: '2b770fbc-76e6-4b5a-afab-882759fd1f06'
513+
},
514+
user: {
515+
userId: 'a4nk945ad'
516+
}
517+
};
518+
const res = {
519+
status: () => { },
520+
json: () => { },
521+
};
522+
const result = {
523+
reaction: false,
524+
userId: 'a4nk945ad',
525+
accommodationId: 'id789ash7sa'
526+
};
527+
const countResult = {
528+
count: 3
529+
};
530+
sinon.stub(res, 'status').returnsThis();
531+
sinon.stub(models.Reaction, 'findOne').returns(result);
532+
sinon.stub(models.Reaction, 'update').returns(true);
533+
sinon.stub(models.Reaction, 'findAndCountAll').returns(countResult);
534+
535+
await AccommodationController.likeAccommodation(req, res);
536+
expect(res.status).to.have.been.calledWith(200);
537+
});
538+
});
539+
540+
describe('Unlike an Accommodation facility', () => {
541+
it('should successfully unlike an existing accommodation', async () => {
542+
const user = await request
543+
.post(signinRoute)
544+
.send(login);
545+
const token = `Bearer ${user.body.data.token}`;
546+
547+
const res = await request
548+
.post(`${accommodationRoute}/2b770fbc-76e6-4b5a-afab-882759fd1f06/unlike`)
549+
.set('Authorization', token);
550+
551+
expect(res).to.have.status(200);
552+
expect(res.body).to.have.property('status');
553+
expect(res.body).to.have.property('message');
554+
});
555+
it('fakes server error when unliking an accommodation facility', async () => {
556+
const req = {
557+
params: {
558+
accommodationId: '2b770fbc-76e6-4b5a-afab-882759fd1f06'
559+
},
560+
user: {
561+
userId: 'a4nk945ad'
562+
}
563+
};
564+
const res = {
565+
status: () => { },
566+
json: () => { },
567+
};
568+
sinon.stub(res, 'status').returnsThis();
569+
sinon.stub(models.Reaction, 'findOne').throws();
570+
571+
await AccommodationController.dislikeAccommodation(req, res);
572+
expect(res.status).to.have.been.calledWith(500);
573+
});
574+
it('fakes a successful dislike of an accommodation', async () => {
575+
const req = {
576+
params: {
577+
accommodationId: '2b770fbc-76e6-4b5a-afab-882759fd1f06'
578+
},
579+
user: {
580+
userId: 'a4nk945ad'
581+
}
582+
};
583+
const res = {
584+
status: () => { },
585+
json: () => { },
586+
};
587+
const result = {
588+
count: 3
589+
};
590+
sinon.stub(res, 'status').returnsThis();
591+
sinon.stub(models.Reaction, 'findOne').returns(null);
592+
sinon.stub(models.Reaction, 'create').returns(true);
593+
sinon.stub(models.Reaction, 'findAndCountAll').returns(result);
594+
595+
await AccommodationController.dislikeAccommodation(req, res);
596+
expect(res.status).to.have.been.calledWith(200);
597+
});
598+
it('fakes a successful removal of unlike from accommodation', async () => {
599+
const req = {
600+
params: {
601+
accommodationId: '2b770fbc-76e6-4b5a-afab-882759fd1f06'
602+
},
603+
user: {
604+
userId: 'a4nk945ad'
605+
}
606+
};
607+
const res = {
608+
status: () => { },
609+
json: () => { },
610+
};
611+
const result = {
612+
reaction: false,
613+
userId: 'a4nk945ad',
614+
accommodationId: 'id789ash7sa'
615+
};
616+
const countResult = {
617+
count: 3
618+
};
619+
sinon.stub(res, 'status').returnsThis();
620+
sinon.stub(models.Reaction, 'findOne').returns(result);
621+
sinon.stub(models.Reaction, 'destroy').returns(true);
622+
sinon.stub(models.Reaction, 'findAndCountAll').returns(countResult);
623+
624+
await AccommodationController.dislikeAccommodation(req, res);
625+
expect(res.status).to.have.been.calledWith(200);
626+
});
627+
it('fakes a successful unliking of accommodation when it was liked previouly', async () => {
628+
const req = {
629+
params: {
630+
accommodationId: '2b770fbc-76e6-4b5a-afab-882759fd1f06'
631+
},
632+
user: {
633+
userId: 'a4nk945ad'
634+
}
635+
};
636+
const res = {
637+
status: () => { },
638+
json: () => { },
639+
};
640+
const result = {
641+
reaction: true,
642+
userId: 'a4nk945ad',
643+
accommodationId: 'id789ash7sa'
644+
};
645+
const countResult = {
646+
count: 3
647+
};
648+
sinon.stub(res, 'status').returnsThis();
649+
sinon.stub(models.Reaction, 'findOne').returns(result);
650+
sinon.stub(models.Reaction, 'update').returns(true);
651+
sinon.stub(models.Reaction, 'findAndCountAll').returns(countResult);
652+
653+
await AccommodationController.dislikeAccommodation(req, res);
654+
expect(res.status).to.have.been.calledWith(200);
655+
});
656+
});
422657
});

0 commit comments

Comments
 (0)