Skip to content

Commit

Permalink
feature(notification): add mark as read
Browse files Browse the repository at this point in the history
Add mark as read endpoint
Add controllers
Write tests

[Finishes #168407551]
  • Loading branch information
benisi committed Sep 11, 2019
1 parent 46f953c commit fd5a0b3
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 99 deletions.
132 changes: 66 additions & 66 deletions package-lock.json

Large diffs are not rendered by default.

19 changes: 14 additions & 5 deletions src/controllers/NotificationController.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import helpers from '../helpers';
import services from '../services';

const { notificationServices: { getUserNotification } } = services;
const { notificationServices: { getUserNotification, markAsRead: markAsReadService } } = services;


const { responseMessage } = helpers;

const getNotification = async (req, res) => {
export const getNotification = async (req, res) => {
const { id } = req.user;
try {
const userNotificationObject = await getUserNotification(id);
responseMessage(res, 200, { notifications: userNotificationObject });
return responseMessage(res, 200, { notifications: userNotificationObject });
} catch (error) {
responseMessage(res, 500, { error: error.message });
return responseMessage(res, 500, { error: error.message });
}
};

export default getNotification;
export const markAsRead = async (req, res) => {
const { id: userId } = req.user;
const id = req.params.id || null;
try {
await markAsReadService(id, userId);
return responseMessage(res, 200, { message: 'Notification was mark as read successfully' });
} catch (error) {
return responseMessage(res, 500, { error: 'Something went wrong' });
}
};
1 change: 1 addition & 0 deletions src/controllers/novelController.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const createNovel = async (request, response) => {
configObjectId: 0,
entityId: createdNovel.id,
followeeId: request.user.id,
actorId: request.user.id,
isSingle: false,
response
};
Expand Down
3 changes: 2 additions & 1 deletion src/database/models/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ export default (sequelize, DataTypes) => {
defaultValue: DataTypes.UUIDV4,
},
notificationObjectId: DataTypes.UUID,
notifierId: DataTypes.UUID
notifierId: DataTypes.UUID,
isRead: DataTypes.BOOLEAN,
}, {});
Notification.associate = (models) => {
Notification.belongsTo(models.User, {
Expand Down
9 changes: 9 additions & 0 deletions src/database/seeders/20190911120324-notificationObject.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const up = queryInterface => queryInterface.bulkInsert('NotificationObjects', [{
id: 'aeab6e66-b8e9-4045-803a-92eced0c56db',
entityId: '929a1f0b-3338-42c0-ade2-71eaa67cc653',
actorId: '11fb0350-5b46-4ace-9a5b-e3b788167915',
entityTypeId: 2,
createdAt: new Date(),
updatedAt: new Date()
}], {});
export const down = queryInterface => queryInterface.bulkDelete('NotificationObjects', null, {});
9 changes: 9 additions & 0 deletions src/database/seeders/20190911124604-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const up = queryInterface => queryInterface.bulkInsert('Notifications', [{
id: 'f83238c9-258f-4c38-a548-5c939a7fdf22',
notificationObjectId: 'aeab6e66-b8e9-4045-803a-92eced0c56db',
notifierId: '8f3e7eda-090a-4c44-9ffe-58443de5e1f8',
isRead: false,
createdAt: new Date(),
updatedAt: new Date()
}], {});
export const down = queryInterface => queryInterface.bulkDelete('Notifications', null, {});
8 changes: 7 additions & 1 deletion src/helpers/validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ const isValidUUIDParam = paramName => param(paramName)
.isUUID()
.withMessage('invalid request');

const isValidUUIDOptional = field => check(field)
.trim()
.optional()
.isUUID()
.withMessage(`${field} must be an UUID`);
/**
* @param {String} field
* @returns {Object} - Express-validator
Expand Down Expand Up @@ -259,5 +264,6 @@ export default {
isValidSubcription,
isValidRoleName,
isValidPasswords,
isValidReadInput
isValidReadInput,
isValidUUIDOptional
};
4 changes: 3 additions & 1 deletion src/middlewares/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import highlightValidator from './highlightValidator';
import verifyToken from './verifyToken';
import authorizeUser from './authorizeUser';
import reportValidator from './reportValidator';
import notificationValidator from './notification';


export default {
Expand All @@ -16,5 +17,6 @@ export default {
highlightValidator,
verifyToken,
authorizeUser,
reportValidator
reportValidator,
notificationValidator,
};
17 changes: 17 additions & 0 deletions src/middlewares/notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import validator from '../helpers/validator';
import errorHandler from './errorHandler';

const {
isValidUUIDOptional
} = validator;

const { validatorError } = errorHandler;

const notificationValidator = {
markAsRead: [
isValidUUIDOptional('id'),
validatorError
]
};

export default notificationValidator;
5 changes: 3 additions & 2 deletions src/routes/notification.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import express from 'express';
import middlewares from '../middlewares';
import getNotification from '../controllers/NotificationController';
import { getNotification, markAsRead } from '../controllers/NotificationController';

const { verifyToken } = middlewares;
const { verifyToken, notificationValidator } = middlewares;

const notification = express.Router();

notification.get('/notifications', verifyToken, getNotification);
notification.patch('/notification/:id?', verifyToken, notificationValidator.markAsRead, markAsRead);

export default notification;
64 changes: 42 additions & 22 deletions src/services/notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import db from '../database/models';
import sendMail from './sendMail';
import helpers from '../helpers';

const { emailNotificationMessage, notificationConfig, responseMessage } = helpers;
const { emailNotificationMessage, notificationConfig } = helpers;

const {
User, Follower, Like, Novel, NotificationObject, Notification, Comment
Expand Down Expand Up @@ -43,7 +43,7 @@ const getNovelData = async (entityId, entity) => {
};

const buildUserNotificationObject = (notifications) => {
const notificationPromise = notifications.map(async ({ notificationObjectId, isRead }) => {
const notificationPromise = notifications.map(async ({ id, notificationObjectId, isRead }) => {
let novelTitle = null, novelUrl = null;
const {
entityId, entityTypeId, actorId, createdAt
Expand All @@ -57,19 +57,41 @@ const buildUserNotificationObject = (notifications) => {
const { firstName, lastName } = await User.findByPk(actorId);

const data = {
actor: `${firstName} ${lastName}`, message, novelTitle, novelUrl, createdAt, isRead
actor: `${firstName} ${lastName}`, message, novelTitle, novelUrl, createdAt, isRead, id
};
return data;
});
return Promise.all(notificationPromise);
};

const getUserNotification = async (id) => {
const notifications = await Notification.findAll({ where: { notifierId: id, isRead: false } });
const notifications = await Notification.findAll({
where: { notifierId: id },
order: [
['createdAt', 'DESC'],
],
});
const userNotificationObject = buildUserNotificationObject(notifications);
return userNotificationObject;
};

const markAsRead = async (id = null, userId) => {
const baseCondition = { isRead: false, notifierId: userId };
const notificationCondition = id
? { ...baseCondition, id }
: baseCondition;
const notifications = await Notification.findAll({ where: notificationCondition });
notifications.forEach(async (notification) => {
Notification.update(
{ isRead: true },
{
where: { id: notification.id },
},
);
});
return true;
};

const broadcastNotification = async (notificationObjectId) => {
const notifications = await Notification.findAll({
where: { notificationObjectId }, include: [{ model: User }]
Expand All @@ -85,6 +107,7 @@ const broadcastNotification = async (notificationObjectId) => {
await sendMail(process.env.ADMIN_MAIL, email, message);
}
});
return true;
};

/**
Expand Down Expand Up @@ -143,29 +166,25 @@ const createUsersNotification = (notificationObjectId, usersToBeNotified) => {
* @returns {boolean} - returns true when all went well
*/
const addNotification = async ({
configObjectId, entityId, followeeId, actorId = null, isSingle = true, response
configObjectId, entityId, followeeId, actorId = null, isSingle = true,
}) => {
if (actorId === followeeId && isSingle) {
return;
}
try {
const entityConfigurationObject = getEntityConfigurationObject(configObjectId);
let usersToBeNotified = null;
if (isSingle) {
usersToBeNotified = [{ userId: followeeId }];
} else {
usersToBeNotified = await getUsersToBeNotified(entityConfigurationObject, followeeId);
}
const notificationObject = await NotificationObject.create({
entityTypeId: configObjectId, entityId, actorId
});
const { id: notificationObjectId } = notificationObject;
createUsersNotification(notificationObjectId, usersToBeNotified);
broadcastNotification(notificationObjectId);
return true;
} catch (error) {
responseMessage(response, 500, { error: error.message });
const entityConfigurationObject = getEntityConfigurationObject(configObjectId);
let usersToBeNotified = null;
if (isSingle) {
usersToBeNotified = [{ userId: followeeId }];
} else {
usersToBeNotified = await getUsersToBeNotified(entityConfigurationObject, followeeId);
}
const notificationObject = await NotificationObject.create({
entityTypeId: configObjectId, entityId, actorId
});
const { id: notificationObjectId } = notificationObject;
createUsersNotification(notificationObjectId, usersToBeNotified);
broadcastNotification(notificationObjectId);
return true;
};


Expand All @@ -178,4 +197,5 @@ export default {
buildUserNotificationObject,
getNovelData,
getEntityConfigurationObject,
markAsRead,
};
42 changes: 41 additions & 1 deletion tests/notification.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const novelId = '7f45df6d-7003-424f-86ec-1e2b36e2fd14';
const {
notificationServices: {
addNotification, getEntityConfigurationObject, getUserNotification, getNovelData,
getUsersToBeNotified,
getUsersToBeNotified
}
} = services;

Expand All @@ -35,6 +35,8 @@ const { expect, should } = chai;
should();

const url = '/api/v1/notifications';
const markNotificationAsReadUrl = '/api/v1/notification';
const invalidUUID = '/api/v1/notification/2cec37fd-feeb-4b01-9bfe-14e81d578k';

const notificationMessageObject = [{
actor: 'John Doe',
Expand Down Expand Up @@ -167,3 +169,41 @@ describe('A test to get novel data', () => {
expect(novelData).to.have.property('id');
});
});

describe('test for marking notification as read', () => {
it('should return a success message', (done) => {
chai.request(app)
.patch(markNotificationAsReadUrl)
.set('authorization', token)
.end((err, res) => {
expect(res).to.have.status(200);
expect(res.body).to.have.property('message');
done();
});
});
it('should return an error for invalid UUID', (done) => {
chai.request(app)
.patch(invalidUUID)
.set('authorization', token)
.end((err, res) => {
expect(res).to.have.status(400);
expect(res.body).to.have.property('errors');
done();
});
});
});
describe('test for handling 500 errors', () => {
it('should return an error 500 when trying to update notification', (done) => {
const stub = sinon.stub(Notification, 'findAll');
stub.throws(new Error('error occured!'));

chai.request(app)
.patch(markNotificationAsReadUrl)
.set('authorization', token)
.end((err, res) => {
expect(res).to.have.status(500);
stub.restore();
done();
});
});
});

0 comments on commit fd5a0b3

Please sign in to comment.