Skip to content

Commit

Permalink
feat(user-chat): Enable users to chat
Browse files Browse the repository at this point in the history
  • Loading branch information
rukundoeric committed Jul 18, 2019
1 parent cc1cacd commit cbab7f6
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 3 deletions.
9 changes: 7 additions & 2 deletions src/api/controllers/auth.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import { omit } from 'lodash';
import sequelize from 'sequelize';
import tokenHelper from '../../helpers/Token.helper';
import HashHelper from '../../helpers/hashHelper';
import db from '../../sequelize/models/index';
Expand All @@ -11,6 +12,7 @@ import workers from '../../workers';
const { generateToken, decodeToken } = tokenHelper;
const { User, Blacklist, Opt } = db;
const { queueEmailWorker } = workers;
const { Op } = sequelize;

dotenv.config();

Expand Down Expand Up @@ -145,8 +147,11 @@ class AuthController {
*/
static async login(req, res) {
const { email } = req.body;
const users = await User.findOne({ where: { email } })
|| await User.findOne({ where: { username: email } });
const users = await User.findOne({
where: {
[Op.or]: [{ email }, { username: email }]
}
});
if (users) {
if (
!HashHelper.comparePassword(req.body.password, users.dataValues.password)
Expand Down
86 changes: 86 additions & 0 deletions src/api/controllers/chatController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import sequelize from 'sequelize';
import db from '../../sequelize/models';

const { Chat, User, follows } = db;
const { Op } = sequelize;
/**
* @author Rukundo Eric
* @class chatController
* @description this class performs chat
*/
class chatController {
/**
*
* @param {Object} req - Request object
* @param {Object} res - Response object
* @return {Object} - Response object
*/
static async getUsers(req, res) {
const { id } = req.user;
follows
.findAll({
where: {
[Op.or]: [{ userId: id }, { followerId: id }]
},
include: [
{
model: User,
as: 'follower',
attributes: ['id', 'firstName', 'lastName', 'email', 'username']
},
{
model: User,
as: 'followedUser',
attributes: ['id', 'firstName', 'lastName', 'email', 'username']
}
]
})
.then((data) => {
if (!data[0]) {
return res.status(200).json({ error: 'you do not have any followers currently' });
}
return res.status(200).json({ followers: data, me: req.user });
});
}

/**
*
* @param {Object} req - Request object
* @param {Object} res - Response object
* @returns {Object} - Response object
*/
static async getMessages(req, res) {
const myId = req.user.id;
const { username } = req.params;
const { dataValues: { id } } = await User.findOne({ where: { username } });
Chat.findAll({
where: {
[Op.or]: [{
senderId: myId,
recieverId: id
},
{
senderId: id,
recieverId: myId
}]
},
include: [
{
model: User,
as: 'sender',
attributes: ['id', 'firstName', 'lastName', 'email', 'username']
},
{
model: User,
as: 'receiver',
attributes: ['id', 'firstName', 'lastName', 'email', 'username']
}
]
}).then((messages) => {
res.status(200).json({
messages
});
});
}
}
export default chatController;
12 changes: 12 additions & 0 deletions src/api/routes/chatRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Router } from 'express';
import chat from '../controllers/chatController';
import auth from '../../middleware/auth';

const { getUsers, getMessages } = chat;
const { verifyToken } = auth;
const chatRouter = Router();

chatRouter.get('/users', verifyToken, getUsers);
chatRouter.get('/:username', verifyToken, getMessages);

export default chatRouter;
3 changes: 2 additions & 1 deletion src/api/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import articlesRouter from './articlesRouter';
import ratingsRouter from './ratingsRouter';
import bookmarkRouter from './bookmark';
import termsAndConditionsRouter from './termsConditionsRouter';
import chatRouter from './chatRouter';

const api = express();

Expand All @@ -17,6 +18,6 @@ api.use('/articles', articlesRouter);
api.use('/ratings', ratingsRouter);
api.use('/bookmarks', bookmarkRouter);
api.use('/termsandconditions', termsAndConditionsRouter);

api.use('/chats', chatRouter);

export default api;
48 changes: 48 additions & 0 deletions src/helpers/SocketIO.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import http from 'http';
import dotenv from 'dotenv';
import socketIO from 'socket.io';
import eventEmitter from './notifications/EventEmitter';
import Tokenizer from './Token.helper';
import chatHelper from './chat/saveChats';

const { saveChat, updateReadMessages, getUnreadMessageCount } = chatHelper;

dotenv.config();

Expand All @@ -22,6 +26,50 @@ const SocketIO = (app) => {
user
});
});
socket.on('user_back', async (token) => {
try {
const currentUser = await Tokenizer.decodeToken(token);
if (currentUser.username) {
const unreadCount = await getUnreadMessageCount(currentUser.username);
if (unreadCount !== 0) {
io.emit('message_unread', {
receiver: currentUser.username,
unreadCount
});
}
}
} catch (error) {
io.emit('no_auth', {
message: 'You are not authenticated'
});
}
});
});

const chats = io.of('/chats');
chats.on('connection', (socket) => {
socket.on('new_user', async (token) => {
try {
const currentUser = await Tokenizer.decodeToken(token);
const count = await getUnreadMessageCount(currentUser.username);
if (count !== 0) {
await updateReadMessages(currentUser.username);
}
if (currentUser.username) {
socket.on('chat', async ({ sender, receiver, message }) => {
await saveChat({ sender, receiver, message });
chats.emit('chat', { sender, message, receiver });
});
socket.on('typing', (data) => {
socket.broadcast.emit('typing', data);
});
}
} catch (error) {
chats.emit('no_auth', {
message: 'You are not authenticated'
});
}
});
});
return io;
};
Expand Down
66 changes: 66 additions & 0 deletions src/helpers/chat/saveChats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import db from '../../sequelize/models';
import findUser from '../FindUser';

const { Chat } = db;

/**
* @author Elie Mugenzi
* @class ChatHelper
* @description this class performs the whole authentication
*/
class ChatHelper {
/**
*
* @param {Object} message - Request object
* @returns {Object} - Response object
*/
static async saveMessage(message) {
const {
sender, receiver, message: chatMessage, read
} = message;
const { id: senderId } = await findUser(sender);
const infos = await findUser(receiver);

const newChat = await Chat.create({
senderId,
recieverId: infos.id,
message: chatMessage,
read
});
return newChat;
}

/**
*
* @param {String} username - Request object
* @returns {Object} - Response object
*/
static async updateReadMessages(username) {
const { id } = await findUser(username);
const result = await Chat.update({
read: true,
}, {
where: {
recieverId: id,
read: false,
}
});
return result;
}

/**
*
* @param {String} username - Request object
* @returns {Number} - Response object
*/
static async getUnreadMessageCount(username) {
const { id } = await findUser(username);
const result = await Chat.count({
where: { recieverId: id, read: false }
});
return result;
}
}


export default ChatHelper;
31 changes: 31 additions & 0 deletions src/sequelize/migrations/20190715084903-create-chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('Chats', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
senderId: {
type: Sequelize.INTEGER
},
message: {
type: Sequelize.STRING
},
recieverId: {
type: Sequelize.INTEGER
},
read: {
type: Sequelize.BOOLEAN
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}),
down: queryInterface => queryInterface.dropTable('Chats')
};
25 changes: 25 additions & 0 deletions src/sequelize/models/chat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* eslint-disable func-names */
module.exports = (sequelize, DataTypes) => {
const Chat = sequelize.define('Chat', {
senderId: DataTypes.INTEGER,
message: DataTypes.STRING,
recieverId: DataTypes.INTEGER,
read: DataTypes.BOOLEAN
}, {});
Chat.associate = function (models) {
// associations can be defined here
Chat.belongsTo(models.User, {
as: 'sender',
foreignKey: 'senderId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
Chat.belongsTo(models.User, {
as: 'receiver',
foreignKey: 'recieverId',
onDelete: 'CASCADE',
onUpdate: 'CASCADE'
});
};
return Chat;
};
21 changes: 21 additions & 0 deletions src/sequelize/seeders/20200618062950-follows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module.exports = {
up: queryInterface => queryInterface.bulkInsert('follows', [{
userId: 5,
followerId: 10,
createdAt: new Date(),
updatedAt: new Date(),
},
{
userId: 10,
followerId: 5,
createdAt: new Date(),
updatedAt: new Date(),
},
{
userId: 3,
followerId: 5,
createdAt: new Date(),
updatedAt: new Date(),
}
], {}),
};
27 changes: 27 additions & 0 deletions src/sequelize/seeders/20200718062950-chats.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module.exports = {
up: queryInterface => queryInterface.bulkInsert('Chats', [{
senderId: 10,
recieverId: 5,
message: 'hello tyhere',
read: true,
createdAt: new Date(),
updatedAt: new Date(),
},
{
senderId: 5,
recieverId: 10,
message: 'How are you?',
read: true,
createdAt: new Date(),
updatedAt: new Date(),
},
{
senderId: 10,
recieverId: 5,
message: 'Im fine',
read: true,
createdAt: new Date(),
updatedAt: new Date(),
}
], {}),
};
Loading

0 comments on commit cbab7f6

Please sign in to comment.