Skip to content

Commit

Permalink
feature(create-notifications):create notifications for travel requests
Browse files Browse the repository at this point in the history
[Finishes #167727462]
  • Loading branch information
jiokeokwuosa committed Sep 18, 2019
1 parent 49c24c7 commit 54c1417
Show file tree
Hide file tree
Showing 14 changed files with 507 additions and 121 deletions.
59 changes: 59 additions & 0 deletions src/controllers/notification.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import models from '../db/models';
import Response from '../utils/response.utils';
import sender from '../services/email.service';
import logger from '../logs/winston';

const { Notification, User } = models;

/**
* This class creates the comment controller
*/
export default class NotificationController {
/**
* Handles creating of new notification
* @param {object} notificationData the data for notification
* @param {object} payload the payload of the email to be sent
* @returns {boolean} true
*/
static async createNotification(notificationData, payload) {
try {
await Notification.create({
...notificationData
}, { returning: true });
const notificationStatus = await NotificationController
.checkNotificationStatus(notificationData.receiver_id);
if (notificationStatus) await sender.sendEmail(process.env.SENDER_EMAIL, payload.manager_email, 'travel_request_notification', payload);
return true;
} catch (error) {
logger.error(error);
}
}

/**
* @param {string} userId The notification details
* @returns {boolean} true or false
*/
static async checkNotificationStatus(userId) {
try {
const { email_notification } = await User.findOne({ where: { id: userId } });
return email_notification;
} catch (error) {
logger.error(error);
}
}

/**
* Handles getting a manager's notification
* @param {ServerRequest} req
* @param {ServerResponse} res
* @returns {ServerResponse} response
*/
static async getNotifications(req, res) {
try {
const result = await Notification.findAll({ attributes: ['id', 'subject', 'notification', 'request_id'], where: { receiver_id: req.body.user_id, read: false } });
return Response.Success(res, result, 200);
} catch (error) {
return Response.InternalServerError(res, 'Could not populate notifications');
}
}
}
69 changes: 55 additions & 14 deletions src/controllers/request.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Sequelize from 'sequelize';
import models from '../db/models';
import sender from '../services/email.service';
import Response from '../utils/response.utils';
import Notification from './notification.controller';

const { Op } = Sequelize;

Expand Down Expand Up @@ -72,24 +73,33 @@ export default class RequestController {
*/
static async createMultiCityRequest(req, res) {
try {
const { id: user_id } = req.currentUser.dataValues;
const { user_id } = req.body;
const {
category, origin, destination, departure_date, return_date, reason, room_id, facility_id
category, origin, destination, departure_date, return_date, reason, booking_id
} = req.body;
const bookingData = {
departure_date, return_date, user_id, room_id, facility_id
};
const booking = await Booking.create(bookingData);
const { id: booking_id } = booking;
const requestData = {
user_id, category, origin, destination: destination.split(', '), departure_date, return_date, reason, booking_id
user_id, category, origin, destination, departure_date, return_date, reason, booking_id
};

requestData.destination = requestData.destination.map((el) => el.toLowerCase());
const request = await Request.create(requestData);
const request = await models.Request.create(requestData);
// send notification
const notificationSubject = `New Travel Request by ${req.body.last_name} ${req.body.first_name}`;
const notificationBody = `${req.body.last_name} ${req.body.first_name} wants to make a multi-city trip`;
const notificationData = {
request_id: request.id,
subject: notificationSubject,
notification: notificationBody,
receiver_id: req.body.manager_id
};
const payload = {
manager_name: `${req.body.manager_last_name} ${req.body.manager_first_name}`,
requester_name: `${req.body.last_name} ${req.body.first_name}`,
manager_email: req.body.manager_email,
user_id: req.body.user_id
};
Notification.createNotification(notificationData, payload);
return res.status(201).json({
status: 'success',
data: { request, booking }
data: { request }
});
} catch (error) {
return res.status(500)
Expand Down Expand Up @@ -144,7 +154,21 @@ export default class RequestController {
booking_id,
user_id
});

const notificationSubject = `New Travel Request by ${req.body.last_name} ${req.body.first_name}`;
const notificationBody = `${req.body.last_name} ${req.body.first_name} wants to make a round trip`;
const notificationData = {
request_id: newRequest.id,
subject: notificationSubject,
notification: notificationBody,
receiver_id: req.body.manager_id
};
const payload = {
manager_name: `${req.body.manager_last_name} ${req.body.manager_first_name}`,
requester_name: `${req.body.last_name} ${req.body.first_name}`,
manager_email: req.body.manager_email,
user_id: req.body.user_id
};
Notification.createNotification(notificationData, payload);
return Response.Success(res, newRequest.dataValues, 201);
} catch (error) {
Response.CustomError(
Expand Down Expand Up @@ -178,7 +202,24 @@ export default class RequestController {

// persist booking to database
Request.create(newRequest)
.then((data) => Response.Success(res, data, 201))
.then((data) => {
const notificationSubject = `New Travel Request by ${req.body.last_name} ${req.body.first_name}`;
const notificationBody = `${req.body.last_name} ${req.body.first_name} wants to make a ${data.category} trip`;
const notificationData = {
request_id: data.id,
subject: notificationSubject,
notification: notificationBody,
receiver_id: req.body.manager_id
};
const payload = {
manager_name: `${req.body.manager_last_name} ${req.body.manager_first_name}`,
requester_name: `${req.body.last_name} ${req.body.first_name}`,
manager_email: req.body.manager_email,
user_id: req.body.user_id
};
Notification.createNotification(notificationData, payload);
Response.Success(res, data, 201);
})
.catch((error) => Response.CustomError(res, 500, 'error',
'Request failed. Please see information below.',
error.message));
Expand Down
4 changes: 4 additions & 0 deletions src/db/seeders/20190828065701-users-seeder.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
preferred_currency: 'NGN',
location: 'Nigeria',
role: 'manager',
manager_id: 1,
created_at: new Date(),
updated_at: new Date()
},
Expand All @@ -27,6 +28,7 @@ module.exports = {
preferred_currency: 'USD',
location: 'USA',
role: 'manager',
manager_id: 2,
created_at: new Date(),
updated_at: new Date()
},
Expand Down Expand Up @@ -71,6 +73,7 @@ module.exports = {
preferred_currency: 'GBP',
location: 'Texas',
role: 'manager',
manager_id: 1,
created_at: new Date(),
updated_at: new Date()
},
Expand All @@ -85,6 +88,7 @@ module.exports = {
preferred_currency: 'GBP',
location: 'Texas',
role: 'manager',
manager_id: 1,
created_at: new Date(),
updated_at: new Date()
},
Expand Down
31 changes: 31 additions & 0 deletions src/middlewares/requests.middleware.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-trailing-spaces */
import models from '../db/models';
import RequestUtils from '../utils/request.utils';
import Response from '../utils/response.utils';
Expand Down Expand Up @@ -31,6 +32,36 @@ export default class RequestMiddleware {
return next();
}

/**
* Prepares search query parameters on the Request object of a
* GET /requests request
* @param {object} req (Request object)
* @param {object} res (ServerResponse object)
* @param {function} next
* @returns {string} values will be passed to the body object on successful completion
*/
static async checkManagerID(req, res, next) {
try {
const { manager_id, first_name, last_name } = await RequestUtils
.getManagerId(req.body.user_id);
if (manager_id == null) {
return Response.UnauthorizedError(res, 'You are not authorized to make a request, kindly update your profile and add your Manager');
}
const { email, first_name: manager_first_name, last_name: manager_last_name } = await RequestUtils
.getManagerDetails(manager_id);
req.body.manager_id = manager_id;
req.body.first_name = first_name;
req.body.last_name = last_name;
req.body.manager_email = email;
req.body.manager_first_name = manager_first_name;
req.body.manager_last_name = manager_last_name;

next();
} catch (error) {
return Response.InternalServerError(res, 'Error occured while checking for manager');
}
}

/**
* Prepares search query parameters on the Request object of a
* GET /requests request
Expand Down
11 changes: 11 additions & 0 deletions src/routes/api/notification.router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import express from 'express';

import NotificationController from '../../controllers/notification.controller';
import Auth from '../../middlewares/auth.middleware';

const router = express.Router();

/* Notification Routes Here */
router.get('/notifications', Auth.verifyToken, NotificationController.getNotifications);

export default router;
7 changes: 4 additions & 3 deletions src/routes/api/requests.router.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ router.patch('/requests/decline/:id',
RequestMiddleware.validateRequests,
RequestController.rejectRequest);

router.post('/requests', Auth.verifyToken, Validator.Requests, RequestController.TripRequests);

router.get('/requests/history', auth.verifyUserToken, RequestController.getTravelHistory);
router.post('/requests', Auth.verifyToken, RequestMiddleware.checkManagerID, Validator.Requests, RequestController.TripRequests);
router.get('/requests/:request_id', Auth.verifyToken, RequestController.getSingleRequest);

router.post('/request/multi-city', auth.verifyUserToken, RequestController.createMultiCityRequest);
router.post('/request/multi-city', Auth.verifyToken, RequestMiddleware.checkManagerID, RequestController.createMultiCityRequest);
router.get('/requests', Auth.verifyToken, RequestMiddleware.prepareRequestQuery, RequestController.findAll);
router.patch('/requests/approve/:id', auth.verifyUserToken, auth.verifyManager, validateRequests.validateRequestsID, RequestMiddleware.validateRequests, RequestController.approveRequest);
router.get('/requests', Auth.verifyToken, RequestController.findAll);


export default router;
5 changes: 3 additions & 2 deletions src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import FacilitiesRouter from './api/facilities.router';
import RatingRouter from './api/rating.router';
import FeedbackRouter from './api/feedback.router';
import RatingAndFeedback from './api/rating_feedback.router';
import NotificationRouter from './api/notification.router';

const router = [
RequestsRoutes,
Expand All @@ -17,7 +18,7 @@ const router = [
RatingRouter,
FeedbackRouter,
BookingsRouter,
RatingAndFeedback
RatingAndFeedback,
NotificationRouter
];

export default router;
8 changes: 4 additions & 4 deletions src/services/email.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import logger from '../logs/winston';
sgMail.setApiKey(process.env.SEND_GRID_API);

const templates = {
travel_request_notification: 'd-56e432ed8cfd4093afbb695458ac2880',
signup_template: 'd-b344aeccd8fd4532a1f03658a92bda50',
request_rejected: 'd-a54b425dc20e42adbe6bd16bdb8dd3aa',
travel_request_notification: 'd-cc74a988ed8543638b08837316ff0048',
signup_template: 'd-015ec1b46e75468897414d8e04cb762a',
request_rejected: 'd-5578c7c2a58f43dba314ad5800cdb00d',
passord_reset: 'd-0e43d73f3e3048bba2d124ff5f384107',
request_approved: 'd-8a49cb87af9b4549ab370fcc42e2874c'
request_approved: 'd-284de148088e4d86bf158a9abcf9d7cf'
};

/**
Expand Down
29 changes: 29 additions & 0 deletions src/test/email.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import chai from 'chai';
import chaiHttp from 'chai-http';
import sinon from 'sinon';
import Sinonchai from 'sinon-chai';
import app from '../index';
import EmailService from '../services/email.service';

chai.use(Sinonchai);
chai.use(chaiHttp);
chai.should();

const { expect } = chai;

describe('/Email service', () => {

it('fakes server email service', (done) => {
const req = { body: {} };
const res = {
status() { },
send() { }
};

sinon.stub(res, 'status').returnsThis();

EmailService.sendEmail(req, res);
(res.status).should.have.callCount(0);
done();
});
});
Loading

0 comments on commit 54c1417

Please sign in to comment.