Skip to content

Commit

Permalink
feat(notification): Send notification to users
Browse files Browse the repository at this point in the history
- write unit test
- send email notification to users
- create database on firebase
- set up firebase configuration
- populate database with user notifications

[Delivers #159206059]
  • Loading branch information
anneKay committed Aug 23, 2018
1 parent 4df1505 commit 7d1fcee
Show file tree
Hide file tree
Showing 24 changed files with 367 additions and 350 deletions.
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,7 @@ AUTH_URI=
TOKEN_URI=
AUTH_PROVIDER_X509_CERT_URL=
CLIENT_X509_CERT_URL=
SECRETE_KEY=
VERIFYTOKEN_EXPIRY=
URL_HOST=
NO_REPLY_MAIL=
5 changes: 2 additions & 3 deletions notification/serviceAccount.js → config/serviceAccount.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@

module.exports = {
export default {
type: 'service_account',
project_id: process.env.PROJECT_ID,
private_key_id: process.env.PRIVATE_KEY_ID,
private_key: process.env.PRIVATE_KEY,
private_key: process.env.PRIVATE_KEY.replace(/\\n/g, '\n'),
client_email: process.env.CLIENT_EMAIL,
client_id: process.env.CLIENT_ID,
auth_uri: process.env.AUTH_URI,
Expand Down
8 changes: 3 additions & 5 deletions controllers/ArticleController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import cloudinary from '../config/cloudinary';
import cloudinary from 'cloudinary';
import Utilities from '../helpers/utilities';
import { Article, User, Like } from '../models';
import createArticleHelper from '../helpers/createArticleHelper';
Expand Down Expand Up @@ -77,7 +76,7 @@ class ArticleController {
return res.status(200).json({ article });
})
.catch(next);
}
}

/**
* get all articles created
Expand Down Expand Up @@ -175,7 +174,7 @@ class ArticleController {

const { slug } = req.params;
let articleId;
let likesCount;
let likesCount = null;
Article.findOne({
where: { slug }
})
Expand Down Expand Up @@ -209,7 +208,6 @@ class ArticleController {
message: 'sorry, you cannot like an article twice',
});
}

res.status(201).json({
success: 'true',
message: 'you successfully liked this article',
Expand All @@ -219,7 +217,7 @@ class ArticleController {
description: article.description,
body: article.body,
userid: article.userid,
likesCount,
likeCount: likesCount,
createdAt: article.createdAt,
updatedAt: article.updatedAt
}
Expand Down
74 changes: 35 additions & 39 deletions controllers/UsersController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { Op } from 'sequelize';
Expand All @@ -7,13 +6,9 @@ import { User, Follow } from '../models';
import utils from '../helpers/utilities';
import sendVerificationEmail from '../helpers/sendmail';

<<<<<<< HEAD
/**
* Class representing users
*/
=======
/** Class representing users */
>>>>>>> feat(notification): users can create notification
export default class UsersController {
/**
* Register a user and return a JWT token
Expand Down Expand Up @@ -103,7 +98,7 @@ export default class UsersController {
* @param {*} next - Incase of errors
* @returns {object} An object containing all the data related to the user
*/
static getProfile(req, res, next) {
static getProfile(req, res) {
User.findOne({ where: { username: req.params.username } })
.then((user) => {
if (!user) {
Expand Down Expand Up @@ -163,39 +158,6 @@ export default class UsersController {
.catch(next);
}

/**
* Verify the email sent to the newly registered user
* @param {*} req - request object
* @param {*} res - response object
* @param {*} next - Next function
* @returns {object} An object containing information indicating if successfull or failed */
static async verifyEmail(req, res) {
const { token } = req.params;
try {
const decodedUserData = jwt.verify(token, process.env.SECRETE_KEY);
const userFound = await db.User.findOne({ where: { id: decodedUserData.id } });
if (userFound) {
if (userFound.isverified) {
return res.status(400).json({
success: false,
errors: {
body: ['You account has already been activated'],
}
});
}
}
db.User.update(
{ isverified: true },
{ where: { id: decodedUserData.id } }
);
return res.status(200).json({ message: 'The user has been verified' });
} catch (err) {
return res.status(400).json({
errors: { body: ['Your verification link has expired or invalid'] }
});
}
}

/**
* @function follow
* @summary Handles the follow feature
Expand Down Expand Up @@ -328,4 +290,38 @@ export default class UsersController {
throw err;
});
}


/**
* Verify the email sent to the newly registered user
* @param {*} req - request object
* @param {*} res - response object
* @param {*} next - Next function
* @returns {object} An object containing information indicating if successfull or failed */
static async verifyEmail(req, res) {
const { token } = req.params;
try {
const decodedUserData = jwt.verify(token, process.env.SECRETE_KEY);
const userFound = await User.findOne({ where: { id: decodedUserData.id } });
if (userFound) {
if (userFound.isverified) {
return res.status(400).json({
success: false,
errors: {
body: ['You account has already been activated'],
}
});
}
}
User.update(
{ isverified: true },
{ where: { id: decodedUserData.id } }
);
return res.status(200).json({ message: 'The user has been verified' });
} catch (err) {
return res.status(400).json({
errors: { body: ['Your verification link has expired or invalid'] }
});
}
}
}
File renamed without changes.
35 changes: 12 additions & 23 deletions helpers/commentNotification.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import nodemailer from 'nodemailer';
import debugLog from 'debug';
import sendgrid from '@sendgrid/mail';

import commentNotificationTemplate from './commentNotifyTemplate';
import sendFirebaseNotification from '../notification';
import { sendNotification } from '../notification';


const debug = debugLog('index');

/**
* @description function to create a string of emails of users that liked an article,
* generate template and send mail to the users
Expand All @@ -31,31 +28,23 @@ export const commentNotifier = (commentDetails) => {
read: false
};

users.forEach(user => sendFirebaseNotification(payload, user.id));
users.forEach(user => sendNotification(payload, user.id));

// generate the email template with comment details
const email = commentNotificationTemplate(commentDetails);

const transport = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: process.env.COMMENTS_NOTIFY_EMAIL,
pass: process.env.COMMENTS_EMAIL_PASSWORD,
},
});
const mailOptions = {
from: process.env.COMMENTS_NOTIFY_EMAIL,
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: usersEmails,
from: '"Authors haven" <noreply@authorhaven.sims.com>',
subject: 'A New Comment on Article!',
text: 'this is text',
html: email,
asm: {
groupId: 7241,
},
};
transport.sendMail(mailOptions, (error) => {
if (error) {
debug('Mail was not sent to users', error);
return null;
}
debug('Mail sent to users....');
});
sendgrid.send(msg);
};

/**
Expand All @@ -77,5 +66,5 @@ export const replyNotifier = (replyData) => {
responseToComment: replyData.comment.body
};

sendFirebaseNotification(payload, replyData.comment.userId);
sendNotification(payload, replyData.comment.userId);
};
8 changes: 0 additions & 8 deletions helpers/commentNotifyTemplate.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,6 @@ const emailTemplate = commentDetails => `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTM
</tr>
</table>
<!-- Sub copy -->
<table class="body-sub">
<tr>
<td>
<p class="sub">If you’re having trouble clicking the button, copy and paste the URL below into your web browser.
</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
Expand Down
36 changes: 18 additions & 18 deletions helpers/createArticleHelper.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import generateUniqueSlug from './generateUniqueSlug';
import { Article, User, Follow } from '../models';
import sendEmail from '../send_an_email';
import sendData from '../notification/index';
import sendEmail from './sendEmail';
import { sendNotification, userData } from '../notification/index';


/**
Expand All @@ -13,7 +13,7 @@ import sendData from '../notification/index';
*/

const createArticleHelper = (res, articleObject, imageUrl = null) => {
let authorId, author, articleTitle, createdArticle;
let authorId, author, articleTitle, articleSlug, createdArticle;
const {
title, description, body, tagList, userId
} = articleObject;
Expand All @@ -38,39 +38,39 @@ const createArticleHelper = (res, articleObject, imageUrl = null) => {
.then((article) => {
authorId = article.userId;
articleTitle = article.title;
articleSlug = article.slug;
author = article.User.username;
createdArticle = article;
return Follow.findAll({
where: { followId: authorId },
include: [{
model: User,
as: 'myFollowers',
where: {
id: 6


},
attributes: ['email', 'id'],
}],
attributes: { exclude: ['id', 'userId', 'followId', 'createdAt', 'updatedAt'] },
raw: true
});
}).then((users) => {
})
.then((users) => {
const emails = users.map(user => user['myFollowers.email']);
const followersId = users.map(user => user['myFollowers.id']);
emails.toString();
sendEmail(emails,
'See what you are missing out on!', author);
const payload = {
title: articleTitle,
source: 'Article',
createdBy: `${author}`,
createdAt: Date.now(),
read: false
};
followersId.forEach((id) => {
sendData(payload, id);
});
if (emails.length > 0 || followersId.length > 0) {
sendEmail(emails, author, articleSlug);
followersId.forEach((id) => {
sendNotification(userData(articleTitle, author), id);
});
}
})
.then(() => {
res.status(201).json({ article: createdArticle });
})
.catch(err => res.status(400).send({
.catch(err => res.status(500).send({
errors: {
body: [
'Sorry, there was an error creating your article',
Expand Down
32 changes: 32 additions & 0 deletions helpers/sendEmail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import sendgrid from '@sendgrid/mail';

import mailtemplate from './articleTemplate';

/**
* get an article using slug as query parameter
* @param {array} emails - array of receivers' email
* @param {string} name - name of author
* @param {string} slug - unique slug of posted article
* @param {string} callback - return value after sending mail
* @returns {error} - error if email is not sent
*/
export default (emails, name, slug, callback) => {
setTimeout(() => {
const templateLink = `${process.env.URL_HOST}/${slug}`;

sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
const msg = {
to: emails,
from: '"Authors haven" <noreply@authorhaven.sims.com>',
subject: 'See what you are missing out on!',
text: 'this is text',
html: mailtemplate.emailTemplate(templateLink, name),
asm: {
groupId: 7240
},
};
sendgrid
.sendMultiple(msg);
callback('Mail address not found');
}, 1000);
};
3 changes: 2 additions & 1 deletion helpers/sendmail.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import nodemailer from 'nodemailer';
import jwt from 'jsonwebtoken';
import winston from 'winston';
import mailtemplate from './mailtemplate';

exports.sendEmail = async (userToBeVerified) => {
Expand Down Expand Up @@ -32,6 +33,6 @@ exports.sendEmail = async (userToBeVerified) => {
}
});
// setup email data with unicode symbols
transporter.sendMail(mailOptions);
transporter.sendMail(mailOptions).then(result => winston.info(result));
});
};
5 changes: 3 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import expressValidator from 'express-validator';
import path from 'path';
import express from 'express';
import bodyParser from 'body-parser';
import cloudinary from 'cloudinary';
import session from 'express-session';
import passport from 'passport';
import cors from 'cors';
import { } from 'dotenv/config';
import errorhandler from 'errorhandler';
import methodOverride from 'method-override';
import morgan from 'morgan';
import debugLog from 'debug';
import expressValidator from 'express-validator';

import { } from 'dotenv/config';
import passportConfig from './config/passport';
import config from './config';

Expand All @@ -38,6 +38,7 @@ cloudinary.config({
api_secret: config.cloudinary.api_secret,
});

passportConfig(app);
app.use(cors());

// Normal express config defaults;
Expand Down
Loading

0 comments on commit 7d1fcee

Please sign in to comment.