Skip to content

Commit

Permalink
feat(notification): User can get in app notification on being followed
Browse files Browse the repository at this point in the history
- create database for in-app notification
- Setup pusher to trigger notification to the listeners(frontend)
- Implement algorithm for notification when a user is followed
[Delivers #167323728 #167347852 #166840929 #167347716]
  • Loading branch information
olorunwalawrence authored and codexempire committed Jul 25, 2019
1 parent eab73eb commit 39cad00
Show file tree
Hide file tree
Showing 20 changed files with 1,174 additions and 316 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"passport-google-oauth20": "^2.0.0",
"pg": "^7.11.0",
"pg-hstore": "^2.3.3",
"pusher": "^2.2.1",
"sequelize": "^5.8.12",
"sequelize-cli": "^5.5.0",
"sinon-chai": "^3.3.0",
Expand Down Expand Up @@ -94,4 +95,4 @@
"prettier-eslint-cli": "^5.0.0",
"sinon": "^7.3.2"
}
}
}
108 changes: 108 additions & 0 deletions src/controllers/notification.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import BaseRepository from '../repository/base.repository';
import responseGenerator from '../helpers/responseGenerator';
import db from '../database/models';

const { User, Notification } = db;

/**
* @class NotificationController
*/
class NotificationController {
/**
* Get all notifications
* @async
* @param {Object} req - Object of the HTTP request
* @param {Object} res - Object of the HTTP response
* @returns {json} Returns json Object
* @static
*/
static async getNotifications(req, res) {
const { id: recieverId } = req.currentUser;
try {
const notification = await BaseRepository.findAll(Notification, {
recieverId
});

return responseGenerator.sendSuccess(res, 200, notification);
} catch (error) {
return responseGenerator.sendError(res, 500, error.message);
}
}

/**
* Mark if the notification was read
* @async
* @param {Object} req - Object of the HTTP request
* @param {Object} res - Object of the HTTP response
* @returns {json} Returns json Object
* @static
*/
static async readNotification(req, res) {
const read = true;
const { notificationId } = req.params;
const { id: receiverId } = req.currentUser;
try {
const markRead = await BaseRepository.update(
Notification,
{ read },
{ id: notificationId, receiverId }
);

if (markRead[0] > 0) {
return responseGenerator.sendSuccess(
res,
200,
null,
'Notification has been read'
);
}
return responseGenerator.sendError(
res,
404,
'Failed to update notification read status'
);
} catch (error) {
return responseGenerator.sendError(res, 500, error.message);
}
}

/**
* Mark if the notification was read
* @async
* @param {Object} req - Object of the HTTP request
* @param {Object} res - Object of the HTTP response
* @returns {json} Returns json Object
* @static
*/
static async toggleNotification(req, res) {
const { emailNotify } = req.body;
const { id } = req.currentUser;
try {
const emailNotification = await BaseRepository.update(
User,
{ emailNotify },
{ id }
);

if (emailNotification && emailNotify) {
return responseGenerator.sendSuccess(
res,
200,
null,
'Email notifications has been enabled'
);
}

return responseGenerator.sendSuccess(
res,
200,
null,
'Email notifications has been disabled'
);
} catch (error) {
return responseGenerator.sendError(res, 500, error.message);
}
}
}

export default NotificationController;
7 changes: 5 additions & 2 deletions src/controllers/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import utility from '../helpers/utils';
import db from '../database/models';
import Pagination from '../helpers/pagination';
import mailer from '../helpers/mailer';
import NotificationHelper from '../helpers/notifications';

const { jwtSigner, verifyPassword } = utility;

const { onFollowNotification } = NotificationHelper;
/**
* @class UserController
*/
Expand Down Expand Up @@ -78,7 +79,7 @@ class UserController {
'Account created successfully. An email verification link has been sent to your email address.'
);
} catch (error) {
return responseGenerator.sendError(res, 500, error);
return responseGenerator.sendError(res, 500, error.message);
}
}

Expand Down Expand Up @@ -288,6 +289,8 @@ class UserController {
const [user, created] = followedUser;

if (created) {
await onFollowNotification(req.currentUser, followeeId);

return responseGenerator.sendSuccess(
res,
200,
Expand Down
7 changes: 2 additions & 5 deletions src/database/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,14 @@ module.exports = {
host: process.env.DB_HOST,
port: process.env.DB_PORT,
dialect: 'postgres',
logging: false,
ssl: true,
define: {
timestamps: false
},
dialectOption: {
ssl: true,
native: true
},
logging: false
}
},
test: {
username: process.env.DB_TEST_USERNAME,
Expand All @@ -35,8 +33,7 @@ module.exports = {
dialectOption: {
ssl: true,
native: true
},
logging: false
}
},
production: {
use_env_variable: 'DATABASE_URL',
Expand Down
35 changes: 35 additions & 0 deletions src/database/migrations/20190717203358-create-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Notifications', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
payload: {
type: Sequelize.JSON
},
read: {
type: Sequelize.BOOLEAN,
defaultValue: false
},
receiverId: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
defaultValue: new Date(),
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
defaultValue: new Date(),
type: Sequelize.DATE
}
});
},
down: (queryInterface /* , Sequelize */) => {
return queryInterface.dropTable('Notifications');
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn('Users', 'emailNotify', {
type: Sequelize.BOOLEAN,
defaultValue: false
});
},

down: (queryInterface /* , Sequelize */) => {
return queryInterface.removeColumn('Users', 'emailNotify');
}
};
7 changes: 3 additions & 4 deletions src/database/models/article.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,10 @@ export default (sequelize, DataTypes) => {

Article.associate = models => {
Article.belongsTo(models.User, {
through: 'Articles',
foreignKey: 'authorId'
foreignKey: 'authorId',
as: 'author'
});
};
Article.associate = models => {

Article.belongsToMany(models.User, {
through: 'Bookmarks',
foreignKey: 'articleId',
Expand Down
4 changes: 2 additions & 2 deletions src/database/models/follower.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ module.exports = (sequelize, DataTypes) => {
foreignKey: 'followerId',
as: 'follower'
});
};
Follower.associate = models => {

Follower.belongsTo(models.User, {
foreignKey: 'followeeId',
as: 'followee'
});
};

return Follower;
};
31 changes: 31 additions & 0 deletions src/database/models/notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module.exports = (sequelize, DataTypes) => {
const Notification = sequelize.define(
'Notification',
{
payload: DataTypes.JSON,
read: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
receiverId: DataTypes.INTEGER,
createdAt: {
allowNull: false,
defaultValue: new Date(),
type: DataTypes.DATE
},
updatedAt: {
allowNull: false,
defaultValue: new Date(),
type: DataTypes.DATE
}
},
{}
);
Notification.associate = models => {
Notification.belongsTo(models.User, {
as: 'notifications',
foreignKey: 'receiverId'
});
};
return Notification;
};
23 changes: 19 additions & 4 deletions src/database/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.ENUM,
values: ['unverified', 'active', 'inactive']
},
emailNotify: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
createdAt: {
allowNull: false,
defaultValue: new Date(),
Expand Down Expand Up @@ -50,26 +54,37 @@ module.exports = (sequelize, DataTypes) => {
as: 'follower',
foreignKey: 'followerId'
});
};
User.associate = models => {

User.belongsToMany(models.User, {
through: 'Followers',
as: 'followee',
foreignKey: 'followeeId'
});

};
User.associate = models => {
User.hasMany(models.Rating, {
foreignKey: 'userId',
as: 'rating'
});
};

User.associate = models => {
User.belongsToMany(models.Article, {
User.hasMany(models.Article, {
foreignKey: 'authorId',
as: 'article'
});
User.hasMany(models.Article, {
through: 'Bookmarks',
foreignKey: 'userId',
as: 'articleId'
});
};

User.associate = models => {
User.hasMany(models.Notification, {
as: 'notifications',
foreignKey: 'receiverId'
});
};
return User;
};
Loading

0 comments on commit 39cad00

Please sign in to comment.