Skip to content

Commit

Permalink
#167313410 Users should be able to receive notifications (#35)
Browse files Browse the repository at this point in the history
*  updated develop

* User should get notifications

* feature(notifications): User should be able to get notifications [Finishes #167313410]

* feature(notifications):Remove unnecessary files [Finishes #167313410]

* feature(notifications):Remove all unnecessary files [Finishes #167313410]

* feature(notifications):Remove nodemailer files [Finishes #167313410]
  • Loading branch information
nkalyesubula authored and Mnickii committed Aug 23, 2019
1 parent 9f9f8fa commit f239c55
Show file tree
Hide file tree
Showing 29 changed files with 643 additions and 14 deletions.
34 changes: 31 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"passport-twitter": "^1.0.4",
"pg": "^7.12.0",
"pg-hstore": "^2.3.3",
"pusher": "^2.2.2",
"request": "^2.87.0",
"sequelize": "^5.11.0",
"sequelize-cli": "^5.5.0",
Expand Down
3 changes: 0 additions & 3 deletions sequelize-data.json

This file was deleted.

3 changes: 3 additions & 0 deletions src/controllers/articles.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import models from '../models';
import Userservice from '../services/user.service';
import articleService from '../services/article.service';
import Helper from '../helpers/helper';
import NotificationServices from '../services/notification.service';
import cloudinaryHelper from '../helpers/cloudinaryHelper';

const { notifyViaEmailAndPush } = NotificationServices;

const db = models.Article;
/**
Expand Down Expand Up @@ -41,6 +43,7 @@ class Articles {
images
};
const createdArticle = await articleService.addArticle(article);
await notifyViaEmailAndPush(req, res, createdArticle.slug);
return res.status(201).json({
status: 201,
article: {
Expand Down
6 changes: 5 additions & 1 deletion src/controllers/comments.controller.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Sequelize from 'sequelize';
import commentsService from '../services/comments.service';
import UserService from '../services/user.service';
import models from '../models';
import NotificationServices from '../services/notification.service';

const { notifyUsersWhoFavorited } = NotificationServices;

const CommentsDb = models.Comment;
const database = models.Article;
/**
Expand Down Expand Up @@ -35,6 +38,7 @@ class Comments {
parentCommentId: req.body.parentCommentId,
};
const createdComment = await commentsService.addComment(comment);
await notifyUsersWhoFavorited(req, res, getArticle.id, req.params.slug);
return res.status(201).send({
status: 201,
message: 'Comment successfully created',
Expand Down
43 changes: 43 additions & 0 deletions src/controllers/favorited.articles.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import models from '../models/index';

const { user, Favorites, Article } = models;

/**
* @description A collection of controller methods for handling favorited articles
* @class ArticleController
*/
class FavoritesController {
/**
* @description controller method for favoriting an article
* @static
* @param {object} req Request object
* @param {object} res Response object
* @returns {Object} a response object
*/
static async createOrRemoveFavorite(req, res) {
try {
const userDetails = await user.findOne({ where: { email: req.auth.email } });
const userId = userDetails.id;
const { articleId } = req.params;

const foundArticle = await Article.findOne({ where: { id: articleId } });
if (!foundArticle) return res.status(404).json({ error: 'Article not found' });

await Favorites.findOrCreate({
where: { articleId, userId },
attributes: ['id', 'articleId', 'userId']
}).spread(async (favorite, created) => {
if (created) {
return res.status(200).json({
message: 'Article favorited successfully'
});
}
await favorite.destroy({ force: true });
return res.status(200).json({ message: 'Favorite removed successfully' });
});
} catch (error) {
return (error);
}
}
}
export default FavoritesController;
35 changes: 35 additions & 0 deletions src/controllers/notifications.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import notificationHelper from '../helpers/notification.helper';


/**
* @description Contains method to allow users opt in and out of notifications
* @export
* @class NotificationController
*/
class NotificationController {
/**
*
* @description Method to opt in and out of email and push notication
* @static
* @param {object} req client request
* @param {object} res server response
* @returns {Object} server response object
* @param {Function} next passes control to the next middleware
* @memberof NotificationController
*/
static async notification(req, res, next) {
const { emailOrInApp } = req.params;

switch (emailOrInApp) {
case 'email':
notificationHelper(req, res, next, 'subscribed');
break;
case 'inApp':
notificationHelper(req, res, next, 'inAppNotification');
break;
default:
return res.status(404).json({ error: 'route does not exist' });
}
}
}
export default NotificationController;
5 changes: 3 additions & 2 deletions src/controllers/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class UserController {
const payload = {
id: theUser.id,
email: theUser.email,
username: theUser.username,
role: theUser.role,
verified: theUser.verified
};
Expand Down Expand Up @@ -187,7 +188,7 @@ class UserController {
const verifyUrl = `${process.env.BACKEND_URL}/api/${
process.env.API_VERSION
}/users/verify?token=${token}`;
sendEmail(payload.email, newUser.username, verifyUrl);
await sendEmail(payload.email, newUser.username, verifyUrl);
return res.status(201).json({
status: 201,
message:
Expand All @@ -196,7 +197,7 @@ class UserController {
token
});
} catch (error) {
const { errors } = error;
const { response: { body: { errors } } } = error;
return res.status(404).send({
status: 404,
message: errors[0].message
Expand Down
40 changes: 40 additions & 0 deletions src/helpers/notification.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import models from '../models/index';


export default async (req, res, next, userColumn) => {
const userDetails = await models.user.findOne({ where: { email: req.auth.email } });
const { id } = userDetails;
try {
const user = await models.user.findOne({ where: { id }, attributes: ['id', 'email', userColumn] });

// This toggles the bolean value
const newNotificationStatus = !user.dataValues[userColumn];

const updatedUser = await models.user.update(
{
[userColumn]: newNotificationStatus,
},
{
where: { id },
returning: true
}
);

if (userColumn === 'subscribed') {
const { subscribed } = updatedUser[1][0].dataValues;
if (subscribed === newNotificationStatus) {
const message = subscribed === true ? 'You have successfully subscribed to our email notifications' : 'You will no longer receive email notifications from us';
return res.status(200).json({ message, statusCode: 200 });
}
}
if (userColumn === 'inAppNotification') {
const { inAppNotification } = updatedUser[1][0].dataValues;
if (inAppNotification === newNotificationStatus) {
const message = inAppNotification === true ? 'You have successfully subscribed to in app notifications' : 'You will no longer receive in-app notifications from us';
return res.status(200).json({ message, statusCode: 200 });
}
}
} catch (error) {
return next(error);
}
};
3 changes: 2 additions & 1 deletion src/helpers/verification-email.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dotenv/config';
import sgMail from '@sendgrid/mail';

const sgMail = require('@sendgrid/mail');

const sendEmail = (email, username, url) => {
sgMail.setApiKey(process.env.SendGridApiKey);
Expand Down
26 changes: 26 additions & 0 deletions src/middlewares/validators/articleId.validation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import Joi from 'joi';
import Util from '../../helpers/util';

const util = new Util();

export default (req, res, next) => {
const { articleId } = req.params;

const schema = {
articleId: Joi.number().positive().required()
};
const { error } = Joi.validate({
articleId
}, schema);

if (!error) return next();
const errorMessageFromJoi = error.details[0].message;
switch (errorMessageFromJoi) {
case '"articleId" must be a number':
util.setError(400, 'articleId must be a non negative integer');
return util.send(res);
default:
util.setError(400, errorMessageFromJoi);
return util.send(res);
}
};
8 changes: 8 additions & 0 deletions src/migrations/20190805142252-create-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ module.exports = {
type: Sequelize.BOOLEAN,
defaultValue: false
},
subscribed: {
defaultValue: false,
type: Sequelize.BOOLEAN
},
inAppNotification: {
defaultValue: false,
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
Expand Down
39 changes: 39 additions & 0 deletions src/migrations/20190822010033-create-app-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('AppNotifications', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
articleSlug: {
type: Sequelize.STRING
},
receiverId: {
type: Sequelize.INTEGER
},
category: {
type: Sequelize.STRING
},
read: {
type: Sequelize.BOOLEAN
},
message: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('AppNotifications');
}
};
30 changes: 30 additions & 0 deletions src/migrations/20190822015903-create-favorites.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('Favorites', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
articleId: {
type: Sequelize.INTEGER
},
userId: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('Favorites');
}
};
14 changes: 14 additions & 0 deletions src/models/appnotification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const AppNotification = sequelize.define('AppNotification', {
articleSlug: DataTypes.STRING,
receiverId: DataTypes.INTEGER,
category: DataTypes.STRING,
read: DataTypes.BOOLEAN,
message: DataTypes.STRING
}, {});
AppNotification.associate = function(models) {
// associations can be defined here
};
return AppNotification;
};
Loading

0 comments on commit f239c55

Please sign in to comment.