Skip to content

Commit

Permalink
Merge 6103dfb into 152461d
Browse files Browse the repository at this point in the history
  • Loading branch information
frankhn committed Apr 20, 2019
2 parents 152461d + 6103dfb commit 044b50c
Show file tree
Hide file tree
Showing 10 changed files with 224 additions and 31 deletions.
69 changes: 54 additions & 15 deletions controllers/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ import mailLinkMaker from '../helpers/mailLinkMaker';
import model from '../models/index';
import Mailer from '../helpers/mailer';
import helper from '../helpers/helper';
import LoggedInUser from '../helpers/LoggedInUser';
import { sendAccountVerification as mailingHelper } from '../helpers/mailing';
/* eslint-disable class-methods-use-this */


const { Op } = Sequelize;
dotenv.config();

const { user: UserModel, userverification: UserVerificationModel, resetpassword: resetPassword } = model;
const {
user: UserModel, userverification: UserVerificationModel,
resetpassword: resetPassword, following: followingModel,
} = model;

/**
* @param { class } User -- User }
Expand All @@ -35,19 +39,19 @@ class User {
const uniqueEmailUsername = helper.handleUsed(emailUsed, userNameUsed);
if (uniqueEmailUsername === true) {
const result = await UserModel.create(newUser);
// Email verification
const verificationHash = mailingHelper(result.email, `${result.firstname} ${result.lastname}`);
const verification = {
userid: result.id,
hash: verificationHash,
status: 'Pending'
};
await UserVerificationModel.create(verification);
//
const following = await followingModel.followings(result.id);
const followers = await followingModel.followers(result.id);
let userAccount = select.pick(result, ['id', 'firstname', 'lastname', 'username', 'email', 'image']);
const token = helper.generateToken(userAccount);
userAccount = select.pick(result, ['username', 'email', 'bio', 'image']);
return helper.authenticationResponse(res, token, userAccount);
return helper.authenticationResponse(res, token, userAccount, following, followers);
}
return res.status(400).json({ error: uniqueEmailUsername });
} catch (error) {
Expand All @@ -71,7 +75,9 @@ class User {
let userAccount = select.pick(user, ['id', 'firstname', 'lastname', 'username', 'email', 'image']);
const token = helper.generateToken(userAccount);
userAccount = select.pick(user, ['username', 'email', 'bio', 'image']);
return helper.authenticationResponse(res, token, userAccount);
const following = await followingModel.followings(user.dataValues.id);
const followers = await followingModel.followers(user.dataValues.id);
return helper.authenticationResponse(res, token, userAccount, following, followers);
}
return res.status(401).json({ error: 'Invalid username or password' });
} catch (error) {
Expand All @@ -97,11 +103,13 @@ class User {
provider: req.user.provider,
provideruserid: req.user.provideruserid
};
const result = await UserModel.socialUsers(ruser);
let userAccount = select.pick(result, ['id', 'firstname', 'lastname', 'username', 'email', 'image']);
const result = await UserModel.so(ruser);
let userAccount = select.pick(result[0].dataValues, ['id', 'firstname', 'lastname', 'username', 'email', 'image']);
const token = helper.generateToken(userAccount);
userAccount = select.pick(result, ['username', 'email', 'bio', 'image']);
return helper.authenticationResponse(res, token, userAccount);
const following = await followingModel.followings(result[0].dataValues.id);
const followers = await followingModel.followers(result[0].dataValues.id);
userAccount = select.pick(result[0].dataValues, ['username', 'email', 'bio', 'image']);
return helper.authenticationResponse(res, token, userAccount, followers, following);
}

/**
Expand All @@ -115,7 +123,7 @@ class User {
// check email existance
const search = await UserModel.checkEmail(email);
if (search === null || undefined) {
res.status(404).json({ message: 'no account related to such email', email });
res.status(404).json({ status: 4040, message: 'no account related to such email', email });
} else {
// generate token
const token = jwt.sign({ id: search.dataValues.id }, process.env.SECRETKEY);
Expand All @@ -127,7 +135,9 @@ class User {
const resetLink = await link.resetPasswordLink();
const result = await new Mailer(email, 'Password reset', resetLink).sender();
res.status(202).json({
message: result, email
status: 202,
message: result,
email
});
}
}
Expand All @@ -141,20 +151,20 @@ class User {
static async resetpassword(req, res) {
const { token } = req.params;
const check = await resetPassword.checkToken(token);
if (!check) { return res.status(400).json({ message: 'invalid token' }); }
if (!check) { return res.status(400).json({ status: 400, message: 'invalid token' }); }
try {
const decode = jwt.verify(token, process.env.SECRETKEY);
const second = (new Date().getTime() - check.dataValues.createdAt.getTime()) / 1000;
if (second > 600) { return res.status(400).json({ message: 'token has expired' }); }
const { password } = req.body;
const result = await UserModel.resetpassword(password, decode.id);
res.status(201).json({
status: 201,
data: result
});
} catch (error) { return res.status(400).json({ message: error.message }); }

} catch (error) { return res.status(400).json({ status: 400, message: error.message }); }
}

/**
* @author Jacques Nyilinkindi
* @param {*} req
Expand All @@ -179,5 +189,34 @@ class User {
return res.status(status).json({ error: `${error.message}` });
}
}

/**
* @author frank harerimana
* @param {*} req
* @param {*} res
* @returns {*} success
*/
static async follow(req, res) {
const bearerHeader = req.headers.authorization;
if (!bearerHeader) { return res.status(401).json({ status: 401, message: 'authorization required' }); }
try {
const { username } = req.params;
const followedUser = await UserModel.checkUser(username);
const followee = await new LoggedInUser(bearerHeader).user();
const checker = await followingModel.finder(followee.id, followedUser.id);
if (!checker) {
await followingModel.newRecord(followee.id, followedUser.id);
} else {
await followingModel.DeleteRe(followee.id, followedUser.id);
}
res.status(201).json({
status: 201,
message: !checker ? 'followed' : 'unfollowed',
follower: followedUser.username
});
} catch (error) {
res.status(400).json({ status: 400, error: 'bad request' });
}
}
}
export default User;
29 changes: 29 additions & 0 deletions helpers/LoggedInUser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';

dotenv.config();
/**
* this class returns the current user
*/
class LoggedInUser {
/**
* @author frank harerimana
* @param {*} token
*/
constructor(token) {
this.token = token;
}

/**
* @returns {*} userId
*/
async user() {
const bearer = this.token.split(' ');
const bearerToken = bearer[1];
const result = bearerToken;
const response = await jwt.verify(result, process.env.SECRETKEY);
return response;
}
}

export default LoggedInUser;
6 changes: 5 additions & 1 deletion helpers/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,11 @@ const validatePassword = (password) => {
return (message === '') ? true : message;
};

const authenticationResponse = (res, token, userData) => res.header('x-auth-token', token).status(200).json({ user: { ...userData, token } });
const authenticationResponse = (res, token, userData, followers, following) => res.header('x-auth-token', token).status(200).json({
user: {
...userData, followers, following, token
}
});

export default {
hashPassword,
Expand Down
28 changes: 28 additions & 0 deletions migrations/20190418190156-create-following.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@


module.exports = {
up: (queryInterface, Sequelize) => queryInterface.createTable('followings', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
followee: {
type: Sequelize.INTEGER
},
follower: {
type: Sequelize.INTEGER
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
}),
// eslint-disable-next-line no-unused-vars
down: (queryInterface, Sequelize) => queryInterface.dropTable('followings')
};
18 changes: 18 additions & 0 deletions models/following.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@


const followingModel = (sequelize, DataTypes) => {
const Following = sequelize.define('following', {
followee: { type: DataTypes.INTEGER, allowNull: false },
follower: { type: DataTypes.INTEGER, allowNull: false }
});
Following.newRecord = (followee, follower) => Following.create({ followee, follower });
Following.finder = (followee, follower) => Following.findOne({ where: { followee, follower } });
Following.followings = followee => Following.count({ where: { followee } });
Following.DeleteRe = (followee, follower) => Following.destroy({ where: { followee, follower } });
Following.followers = follower => Following.count({ where: { follower } });
// Following.associate = function (models) {
// // associations can be defined here
// };
return Following;
};
export default followingModel;
16 changes: 6 additions & 10 deletions models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ const UserModel = (Sequelize, DataTypes) => {
id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true },
firstname: { type: DataTypes.STRING, allowNull: true },
lastname: { type: DataTypes.STRING, allowNull: true },
username: { type: DataTypes.STRING, allowNull: false },
username: { type: DataTypes.STRING, allowNull: false, unique: true },
email: {
type: DataTypes.STRING,
allowNull: true,
unique: true,
validate: {
validation() {
if (!(this.provider)) {
Expand Down Expand Up @@ -38,20 +39,15 @@ const UserModel = (Sequelize, DataTypes) => {
provideruserid: { type: DataTypes.STRING, allowNull: true, },
verified: { type: DataTypes.BOOLEAN, allowNull: false, defaultValue: false }
});
User.socialUsers = async (userProfile) => {
const result = await User.findOrCreate({
where: { provideruserid: userProfile.provideruserid },
defaults: userProfile
});
return result[0].dataValues;
};
User.so = us => User.findOrCreate({ where: { provideruserid: us.provideruserid }, defaults: us });
User.associate = (models) => {
User.hasMany(models.article, {
foreignKey: 'authorid', onDelete: 'CASCADE'
});
};
User.checkEmail = async email => User.findOne({ where: { email } });
User.resetpassword = async (password, id) => User.update({ password }, { where: { id } });
User.checkEmail = email => User.findOne({ where: { email } });
User.resetpassword = (password, id) => User.update({ password }, { where: { id } });
User.checkUser = username => User.findOne({ where: { username } });
return User;
};
export default UserModel;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "A Social platform for the creative at heart",
"main": "index.js",
"scripts": {
"test": "NODE_ENV=test nyc --reporter=html --reporter=text mocha --require @babel/polyfill --require @babel/register ./test/*.js --exit",
"test": "NODE_ENV=test npm run migrate && nyc --reporter=html --reporter=text mocha --require @babel/polyfill --require @babel/register ./test/*.js --exit",
"start": "babel-node ./index.js",
"migrate": "node_modules/.bin/sequelize db:migrate:undo:all && node_modules/.bin/sequelize db:migrate",
"dev": "nodemon --exec babel-node ./index.js ",
Expand Down
8 changes: 8 additions & 0 deletions routes/api/following.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import express from 'express';
import user from '../../controllers/user';

const router = express.Router();

router.post('/:username/follow', user.follow);

export default router;
2 changes: 2 additions & 0 deletions routes/routes.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import express from 'express';
import articleRoutes from './api/articles';
import authentication from './api/authentication';
import follwingRoute from './api/following';

const app = express();

app.use('/auth', authentication);
app.use('/articles', articleRoutes);
app.use('/profiles', follwingRoute);

export default app;
Loading

0 comments on commit 044b50c

Please sign in to comment.