Skip to content

Commit

Permalink
Merge pull request #35 from andela/feature/164599833/user-email-notif…
Browse files Browse the repository at this point in the history
…ication

#164599833 user notification
  • Loading branch information
aanchirinah committed Mar 18, 2019
2 parents 49b992a + d96c19e commit 4eed1ee
Show file tree
Hide file tree
Showing 14 changed files with 447 additions and 7 deletions.
12 changes: 11 additions & 1 deletion bin/www.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
import '@babel/polyfill';
import server from '../index';
import http from 'http';
import socketIo from 'socket.io';
import app from '../index';
import { env } from '../helpers/utils';
import logger from '../helpers/logger';

const port = env('PORT', 3000);
const server = http.createServer(app);
server.listen(port, () => logger.log(`Running on port ${port}`));

// add socket connection
const io = socketIo(server);
io.on('connection', (socket) => {
// make socket global, so that new emitters can be created
global.socket = socket;
});
137 changes: 137 additions & 0 deletions controllers/notificationsController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import logger from '../helpers/logger';
import models from '../models/index';
import Mail from '../helpers/sendMail';
import Response from '../helpers/responseHelper';
import { STATUS } from '../helpers/constants';
/**
* @description This class represents a notification in the app
* @class NotificationsController
*/
class NotificationsController {
/**
* This function gets a user setting
* @param {Number} id - The message to be sent
* @returns {Promise} - returns a promise
*/
static async getSetting(id) {
const userSetting = await models.Setting.findOne({ where: { userId: id } });
return userSetting;
}

/**
* This function gets a users email
* @param {Number} id - The message to be sent
* @returns {String} - returns user email
*/
static async getEmail(id) {
const userData = await models.User.findOne({ where: { id } });
const { dataValues } = userData;
return (dataValues.email);
}


/**
* This function creates a new notification
* It checks if the user is ok with getting notifications
* By default a user gets in-app notifications if the user wants to recieve them in general
* It checks what type of notification a user wants to get and sends it
* If notification is to be sent to one user, then put the id in an array.
* @param {String} message - The message to be sent
* @param {Array} userIds - The users to send notifications to
* @returns {void}
*/
static async create(message, userIds) {
// loop through each user
// eslint-disable-next-line no-restricted-syntax
for (const id of userIds) {
// for a single id
// get the user settings
// eslint-disable-next-line no-await-in-loop
const userSettingsData = await this.getSetting(id);
if (userSettingsData != null) {
const { dataValues } = userSettingsData;
// check if the user can recieve notification
if (dataValues.canNotify) {
// check if the user can recieve email notification
if (dataValues.canEmail) {
// get the email of the user
// eslint-disable-next-line no-await-in-loop
const email = await this.getEmail(id);
// send the user an email notification
this.sendMailNotification(email, message);
}
// send default in app mail
// eslint-disable-next-line no-await-in-loop
await this.sendInAppNotification(id, message);
}
}
}
}

/**
* This function sends an email notification to a user
* @param {String} email - The email of the user
* @param {String} message - This is the notification message
* @returns {Boolean} - returns a boolean
*/
static async sendMailNotification(email, message) {
const data = {
email,
subject: 'Notification',
mailContext: {
message
},
template: 'notification'
};
try {
await Mail.sendMail(data);
return true;
} catch (e) {
logger.log(e);
return false;
}
}

/**
* This function sends an in-app notification to a user
* @param {String} id - The user id
* @param {String} message - This is the notification message
* @returns {void}
*/
static async sendInAppNotification(id, message) {
try {
await models.Notification.create({ message, userId: id });
} catch (e) {
logger.log(e);
}
if (global.socket) {
await global.socket.emit(`notification${id}`, { message });
}
}

/**
* This function gets an authenticated users notifications from the database
* @param {Request} request - request object
* @param {Response} response - response object
* @returns {Response} response object
*/
static async getAllNotifications(request, response) {
// get user id
const userId = request.user.id;
// get users notifications
try {
const notificationData = await models.Notification.findAll({
where: { userId },
raw: true
});
if (notificationData == null) {
return Response.send(response, STATUS.OK, null, 'No notifications');
}
return Response.send(response, STATUS.OK, notificationData, 'Successful');
} catch (e) {
logger.log(e);
return Response.send(response, STATUS.BAD_REQUEST, null, 'An error occured', false);
}
}
}
export default NotificationsController;
22 changes: 22 additions & 0 deletions controllers/usersController.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Mail from '../helpers/sendMail';
import Response from '../helpers/responseHelper';
import { STATUS, MESSAGE } from '../helpers/constants';
import logger from '../helpers/logger';
import NotificationsController from './notificationsController';

const { User } = models;

Expand Down Expand Up @@ -208,6 +209,27 @@ class UsersController {
}
return Response.send(response, STATUS.OK, null, MESSAGE.ACCOUNT_CONFIRM);
}

/**
* This function sends a user test notififcation
* @static
* @param {Request} request - Request object
* @param {Response} response - Response object
* @param {function} next - Express next function
* @returns {void}
*/
static async sendUsersTestNotification(request, response) {
// get all users on the platform
const userData = await User.findAll({ attributes: ['id'] });
const userIdsArray = [];
userData.forEach((data) => {
const { dataValues } = data;
const userIds = dataValues;
userIdsArray.push(userIds.id);
});
const res = await NotificationsController.create('Hello welcome to AH.', userIdsArray);
return res;
}
}

export default UsersController;
34 changes: 34 additions & 0 deletions database/migrations/20190313141157-create-notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@


module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Notifications', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
message: {
type: Sequelize.STRING,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
},
userId: {
allowNull: false,
type: Sequelize.INTEGER,
onDelete: 'CASCADE',
references: {
model: 'Users',
key: 'id',
}
}
}),
down: (queryInterface, Sequelize) => queryInterface.dropTable('Notifications')
};
8 changes: 5 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import passport from 'passport';
import logger from './helpers/logger';
import { MESSAGE } from './helpers/constants';
import exceptionHandler from './middlewares/exceptionHandler';
import models from './models';
import models from './models/index';
import routes from './routes';

const isProduction = process.env.NODE_ENV === 'production';
// Routes
import routes from './routes';


// Create global app object
const app = express();

const isProduction = app.get('env') === 'production';

logger.config();
app.use(cors());
Expand Down Expand Up @@ -91,4 +92,5 @@ app.use((req, res, next) => {

app.use(exceptionHandler);


export default app;
39 changes: 39 additions & 0 deletions models/Notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* A model class representing user resource
*
* @param {Sequelize} sequelize - Sequelize object
* @param {Sequelize.DataTypes} DataTypes - A convinient object holding data types
* @return {Sequelize.Model} - User model
*/

const Notification = (sequelize, DataTypes) => {
/**
* @type {Sequelize.Model}
*/
const NotificationSchema = sequelize.define('Notification', {
message: {
type: DataTypes.STRING,
allowNull: false
},
createdAt: {
type: DataTypes.DATE,
defaultValue: sequelize.NOW
},
updatedAt: {
type: DataTypes.DATE,
defaultValue: sequelize.NOW,
onUpdate: sequelize.NOW
}
}, {});

NotificationSchema.associate = (models) => {
NotificationSchema.belongsTo(models.User, {
foreignKey: 'userId',
targetKey: 'id',
onDelete: 'CASCADE'
});
};
return NotificationSchema;
};

export default Notification;
4 changes: 2 additions & 2 deletions models/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ Object.keys(db).forEach((modelName) => {

db.sequelize = sequelize;
db.Sequelize = Sequelize;

export default db;
const models = db;
export default models;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"sinon-express-mock": "^2.1.0",
"slug": "^0.9.1",
"slugify": "^1.3.4",
"socket.io": "^2.2.0",
"swagger-jsdoc": "^3.2.7",
"underscore": "^1.9.1",
"uuid": "^3.3.2",
Expand Down
2 changes: 2 additions & 0 deletions routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import profile from './profile';
import authRoute from './auth';
import articles from './articles';
import settingRouter from './settings';
import notificationsRouter from './notifications';

const router = express.Router();

Expand All @@ -27,5 +28,6 @@ router.use('/articles', articles);
router.use('/auth', authRoute);
router.use('/articles', articles);
router.use('/setting', settingRouter);
router.use('/notification', notificationsRouter);

export default router;
34 changes: 34 additions & 0 deletions routes/notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import express from 'express';
import notificationsController from '../controllers/notificationsController';
import middlewares from '../middlewares';

const notificationsRouter = express.Router();


/**
* /api/v1/users/confirm_account:
* get:
* tags:
* - Notifications
* description: Get all of a users notifications
* produces:
* - application/json
* parameters:
* - name: Authorization
* description: Auth token
* in: header
* required: true
* type: string
* responses:
* 200:
* description: OK
* schema:
* type: object
*/
notificationsRouter.get(
'/',
middlewares.authenticate,
notificationsController.getAllNotifications
);

export default notificationsRouter;
2 changes: 1 addition & 1 deletion routes/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ router.post(
*/
router.get('/confirm_account', UsersController.confirmUser);


router.get('/sendmail', UsersController.sendUsersTestNotification);
/**
* @swagger
* /api/v1/users/reset_password:
Expand Down
Loading

0 comments on commit 4eed1ee

Please sign in to comment.