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 20, 2018
1 parent b5cb54f commit d9c4594
Show file tree
Hide file tree
Showing 41 changed files with 1,862 additions and 450 deletions.
4 changes: 4 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ AUTH_URI=
TOKEN_URI=
AUTH_PROVIDER_X509_CERT_URL=
CLIENT_X509_CERT_URL=
SECRETE_KEY=
VERIFYTOKEN_EXPIRY=
URL_HOST=
NO_REPLY_MAIL=
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,5 @@ dist/
# enviroment variables
.env

#txt
txt.txt
70 changes: 45 additions & 25 deletions config/passport.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,51 @@
import passport from 'passport';
import bcrypt from 'bcrypt';
import local from 'passport-local';
import db from '../models';
import GoogleStrategy from 'passport-google-oauth20';
import FacebookStrategy from 'passport-facebook';
import AuthController from '../controllers/authController';
import { User } from '../models';

const LocalStrategy = local.Strategy;

passport.use(new LocalStrategy(
{
usernameField: 'user[email]',
passwordField: 'user[password]'
},
((email, password, done) => {
db.User.findOne({ where: { email } })
.then((user) => {
bcrypt.compare(
password,
user.hashedPassword,
(err, res) => {
if (!user || !res || err) {
return done(null, false, {
errors: { 'email or password': 'is invalid' }
});
}
return done(null, user);
const passportConfig = (app) => {
app.use(passport.initialize());
app.use(passport.session());
passport.use(new LocalStrategy(
{
usernameField: 'user[email]',
passwordField: 'user[password]'
},
((email, password, done) => {
User.findOne({ email })
.then((user) => {
if (!user || !user.validPassword(password)) {
return done(null, false, {
errors: { 'email or password': 'is invalid' }
});
}
);
})
.catch(done);
})
));

return done(null, user);
})
.catch(done);
})
));

/**
* configurations for passport strategies
*/

passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/api/users/login/google/redirect',
}, AuthController.strategyCallback));

passport.use(new FacebookStrategy({
clientID: process.env.FACEBOOK_APP_ID,
clientSecret: process.env.FACEBOOK_APP_SECRET,
callbackURL: '/api/users/login/facebook/redirect',
profileFields: ['id', 'displayName', 'photos', 'email'],
}, AuthController.strategyCallback));
};

export default passportConfig;
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
7 changes: 3 additions & 4 deletions controllers/ArticleController.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class ArticleController {
success: true,
article: result[1]
}))
.catch((err) => {
.catch(() => {
res.status(500).json({
errors: {
body: [
Expand Down Expand Up @@ -191,7 +191,7 @@ class ArticleController {

const { slug } = req.params;
let articleId;
let likesCount;
let likesCount = null;
Article.findOne({
where: { slug }
})
Expand Down Expand Up @@ -225,7 +225,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 @@ -235,7 +234,7 @@ class ArticleController {
description: article.description,
body: article.body,
userid: article.userid,
likesCount,
likeCount: likesCount,
createdAt: article.createdAt,
updatedAt: article.updatedAt
}
Expand Down
44 changes: 41 additions & 3 deletions controllers/UsersController.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@

import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { Op } from 'sequelize';
// import db from '../models';
import { User, Follow } from '../models';
import utils from '../helpers/utilities';
import sendVerificationEmail from '../helpers/sendmail';

/** Class representing users */
/**
* Class representing users
*/
export default class UsersController {
/**
* Register a user and return a JWT token
Expand All @@ -32,6 +35,7 @@ export default class UsersController {
hashedPassword: hash,
}).then((registeredUser) => {
const token = utils.signToken({ id: registeredUser.id });
sendVerificationEmail.sendEmail(registeredUser);
res.status(200).json(utils.userToJson(registeredUser, token));
}).catch(next);
} else {
Expand Down Expand Up @@ -94,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 @@ -284,4 +288,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'] }
});
}
}
}
98 changes: 98 additions & 0 deletions controllers/authController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { User } from '../models';
import utilities from '../helpers/utilities';
/**
* @class AuthController
*
* @export
*
*/
export default class AuthController {
/**
* @description - json response function
* @static
*
*
* @param {object} req
* @param {object} res
*
* @returns {json} json
*
* @memberof AuthController
*
*/
static jsonResponse(req, res) {
const user = {
email: req.user.email,
token: utilities.signToken({ id: req.user.id }),
firstname: req.user.firstname,
lastname: req.user.lastname,
username: req.user.username,
image: req.user.image,
};
if (req.user.created) {
return res.status(201).json({ success: true, user });
}
return res.status(200).json({ success: true, user });
}

/**
* @description - model query function
* @static
*
*
* @param {object} user
* @param {function} done
*
* @returns {object} newOrFoundUser
*
* @memberof AuthController
*
*/
static queryModel(user, done) {
User.findOrCreate({
where: {
email: user.email,
},
defaults: user,
}).spread((newOrFoundUser, created) => {
const {
id, email, username, lastname, firstname, image,
} = newOrFoundUser.dataValues;
return done(null, {
id,
email,
username,
lastname,
firstname,
image,
created,
});
});
}


/**
* @description - strategy callback function
* @static
*
* @param {object} accessToken
* @param {object} refreshToken
* @param {object} profile
* @param {function} done
*
* @returns {json} json
*
* @memberof AuthController
*
*/
static strategyCallback(accessToken, refreshToken, profile, done) {
const userProfile = {
firstname: profile.name.familyName,
lastname: profile.name.givenName,
username: profile.id,
email: profile.emails[0].value,
image: profile.photos[0].value,
};
AuthController.queryModel(userProfile, done);
}
}
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
Loading

0 comments on commit d9c4594

Please sign in to comment.