Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation for user comment notification #54

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
],
"reporter": [
"lcov",
"text"
"text",
"text-summary"
],
"cache": false,
"require": [
Expand Down
17 changes: 15 additions & 2 deletions src/controllers/CommentController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import UserRepository from '../repositories/UserRepository';
import CommentRepository from '../repositories/CommentRepository';
import TripRequestRepository from '../repositories/TripRequestRepository';
import NotificationRepository from '../repositories/NotificationRepository';

import { sendSuccessResponse, sendErrorResponse } from '../utils/sendResponse';

Expand Down Expand Up @@ -35,17 +36,29 @@ class CommentController {
const tripRequestDetails = await TripRequestRepository.findById({ uuid: tripRequestUuid });
if (!tripRequestDetails) return sendErrorResponse(res, 404, 'This trip request does not exist');
const { user_uuid: tripRequestOwner } = tripRequestDetails;


// ensuring ownership
const isAllowed = await checkrequestOwner(tripRequestOwner, userUuid, userRole);
if (!isAllowed && userRole === 'Manager') return sendErrorResponse(res, 403, 'You are not the manager of the user that created this trip request');
if (!isAllowed && userRole !== 'Manager') return sendErrorResponse(res, 403, 'You can\'t comment on a trip request you did not create');


// creating comment
const commentDetails = {
user_uuid: userUuid,
trip_request_uuid: tripRequestUuid,
message
};

// create notification
const notificationDetails = {
user_uuid: userUuid,
message,
status: 'unread',
notification_type: 'comment'
};

const createdComment = await CommentRepository.create(commentDetails);
await NotificationRepository.create(notificationDetails);
return sendSuccessResponse(res, 201, createdComment);
} catch (err) {
return next(err);
Expand Down
99 changes: 99 additions & 0 deletions src/controllers/CommentNotificationController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable */
import Model from '../models';
import { setHeaders, addHook } from '../utils/NotificationHelpers';
import tripRepository from '../repositories/TripRequestRepository';
import NotificationRepository from '../repositories/NotificationRepository';
import dotenv from 'dotenv';

dotenv.config();

const { Comment } = Model;

/**
* @description Comment controller
*/
class CommentNotifications {
/**
* @param {Object} req - HTTP request object
*
* @param {Object} res - HTTP response object
*
* @param {Function} next - Function to trigger next middleware
*
* @return {Object} Return success message and account creation status
*/

constructor() {
this.model = Comment;
}

/**
* @description Add notifications for comment model
*
* @param {Object} req - HTTP request object
*
* @param {Object} res - HTTP response object
*
* @param {Function} next - Function to execute next function
*
* @returns {Void} Nothing is been returned. Just sets hooks to a given model
*/
async create (req, res, next) {

const appBaseURL = process.env['APP_BASE_PATH'];
let tripsObj = [];
const currentUserUUID = req.userData.uuid;

// Set HTTP headers for SSE
setHeaders(req, res);

try {
// Get all unread notifications
const notifs = await NotificationRepository.getAll({
user_uuid: currentUserUUID,
status: 'unread',
notification_type: 'comment'
});

// Construct comment summary and link to comment.
notifs.reduce((trips, currentTrip)=>{
return tripsObj.push({
uuid: currentTrip.uuid,
message: currentTrip.message,
link: `${appBaseURL}/trips/${currentTrip.uuid}`
})
}, tripsObj);
// Send unread comments to user
res.write('event: comment\n');
res.write(`data: ${JSON.stringify(tripsObj)}\n\n`);

// Method to execute for every comment creation
const helper = async (comment) => {
const {dataValues: {trip_request_uuid}} = comment;

const {
dataValues: {
user_uuid: requester_id,
uuid: trip_uuid
}
} = await tripRepository.findById({uuid: trip_request_uuid});

if(requester_id == currentUserUUID){
res.write(`event: comment\n`);
res.write(`data: ${JSON.stringify({
message: comment.message,
link: `${appBaseURL}/trips/${trip_uuid}`
})}\n\n`);
}
}

// Add realtime notification. Triggered after new comment is created.
addHook(Comment, 'afterCreate', 'notification', helper);

} catch (error) {
next(error);
}
}
}

export default new CommentNotifications();
75 changes: 0 additions & 75 deletions src/controllers/ExampleUserNotificationController.js

This file was deleted.

10 changes: 10 additions & 0 deletions src/database/seeders/20190825080303-create-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ export default {
created_at: Sequelize.literal('NOW()'),
updated_at: Sequelize.literal('NOW()')
},
{
uuid: uuid(),
name: 'Wokoro Samuel Douye',
email: '[email protected]',
role: 'Requester',
is_verified: true,
password: hashPassword('Samsizzy777'),
created_at: Sequelize.literal('NOW()'),
updated_at: Sequelize.literal('NOW()')
},
{
uuid: uuid(),
name: 'Makaraba Blessing',
Expand Down
33 changes: 33 additions & 0 deletions src/database/seeders/20190913015500-notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import uuid from 'uuid';

'use strict';

module.exports = {
up: (queryInterface, Sequelize) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'Sequelize' is defined but never used no-unused-vars

const notificationData = [
{
uuid: uuid(),
user_uuid: 'abef6009-48be-4b38-80d0-b38c1bc39922',
message: 'Can i have my trip request approved?',
status: 'unread',
notification_type: 'comment',
created_at: new Date(),
updated_at: new Date()
},
{
uuid: uuid(),
user_uuid: 'abef6009-48be-4b38-80d0-b38c1bc39922',
message: 'Please i need my trip request approved',
status: 'unread',
notification_type: 'comment',
created_at: new Date(),
updated_at: new Date()
}
];
return queryInterface.bulkInsert('Notifications', notificationData, {});
},

down: (queryInterface, Sequelize) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'Sequelize' is defined but never used no-unused-vars
Unexpected block statement surrounding arrow body; move the returned value immediately after the => arrow-body-style

return queryInterface.bulkDelete('Notifications', null, {});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected indentation of 4 spaces but found 6 indent

}
};
13 changes: 3 additions & 10 deletions src/routes/notifications.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
import { Router } from 'express';

import userNotificationController from '../controllers/ExampleUserNotificationController';
import authenticateUser from '../middlewares/authenticateUser'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon semi

import commentNotificationController from '../controllers/CommentNotificationController';

const notificationRoutes = Router();

// Live notification endpoint for user creation.
notificationRoutes.get('/user', userNotificationController.create);

// Example Live notification endpoint for request.

// notificationRoutes.get(
// '/request',
// authenticateUser,
// notificationController.requestNotifications
// );
notificationRoutes.get('/comment', authenticateUser, commentNotificationController.create);

export default notificationRoutes;
56 changes: 56 additions & 0 deletions tests/comment.notification.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { describe, it } from 'mocha';
import sinon from 'sinon';
import sinonChai from 'sinon-chai';
import chai, { expect } from 'chai';
import chaiHttp from 'chai-http';
import CommentNotificationController from '../src/controllers/CommentNotificationController';
import {req, res, next} from './mock.data';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A space is required after '{' object-curly-spacing
A space is required before '}' object-curly-spacing

import NotificationRepository from '../src/repositories/NotificationRepository';
import tripRepository from '../src/repositories/TripRequestRepository';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'tripRepository' is defined but never used no-unused-vars


chai.use(chaiHttp);
chai.use(sinonChai);

describe('Comment notification', () => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block must not be padded by blank lines padded-blocks


before(()=>{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after => arrow-spacing
Missing space before => arrow-spacing

sinon.spy(res, 'writeHead');
sinon.spy(res, 'write');
});

after(()=>{ sinon.restore(); });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after => arrow-spacing
Missing space before => arrow-spacing


it('it should set headers should be set', ()=>{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after => arrow-spacing
Missing space before => arrow-spacing

CommentNotificationController.create(req, res, next);
expect(res.writeHead).to.be.calledWith(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
});
expect(res.write).to.be.calledWith('\n');
});

it('it should return unread comments', ()=>{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Block must not be padded by blank lines padded-blocks
Missing space after => arrow-spacing
Missing space before => arrow-spacing


sinon.stub(NotificationRepository, 'getAll').returns([
{
uuid: 'abef6009-48be-4b38-80d0-b38c1bc39922',
user_uuid: 'abef6009-48be-4b38-80d0-b38c1bc39922',
message: 'Please i need my trip request approved',
status: 'unread',
notification_type: 'comment',
created_at: new Date(),
updated_at: new Date()
}
]);
CommentNotificationController.create(req, res, next);
expect(res.write.called).to.be.true;
NotificationRepository.getAll.restore();
});

it('it should call next function for server error', ()=>{
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after => arrow-spacing
Missing space before => arrow-spacing

sinon.stub(NotificationRepository, 'getAll').throws();
expect(CommentNotificationController.create).throws
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon semi

NotificationRepository.getAll.restore();
});
});
3 changes: 3 additions & 0 deletions tests/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import './office.test';
// Password reset
import './passwordReset.test';

// Comment notification
import './comment.notification.test';

// UTILITIES

// Getting user info test
Expand Down
1 change: 1 addition & 0 deletions tests/request.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -398,5 +398,6 @@ describe('Test Create Trip Request', () => {
expect(next.called).to.true;
sinon.restore();
});

});
});