Skip to content
This repository has been archived by the owner on May 9, 2021. It is now read-only.

Commit

Permalink
Merge a022194 into ae6ae88
Browse files Browse the repository at this point in the history
  • Loading branch information
augustineezinwa committed Nov 13, 2018
2 parents ae6ae88 + a022194 commit 7eff883
Show file tree
Hide file tree
Showing 16 changed files with 477 additions and 2 deletions.
14 changes: 14 additions & 0 deletions mockdata/followMockData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const firstFollowMockData = {
authorId: 1,
followerId: 2
};

export const secondFollowMockData = {
authorId: 1,
followerId: 3
};

export const thirdFollowMockData = {
authorId: 2,
followerId: 90000
};
2 changes: 2 additions & 0 deletions mockdata/userMockData.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,5 @@ export const userDataWithVeryLongBio = {
dfdjfdjfjdpfjkdpjfdpfjpdjfdpfjpdjfdoipjfdopjfdpjfdifjdpfjdjf
dofjdpjfpdjfpdjfdjfpdjfpdjfdpoijfdsjwfpei3er//3erfje3kfkdkf`,
};

export const userData = {};
22 changes: 22 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"method-override": "^2.3.10",
"methods": "^1.1.2",
"morgan": "^1.9.1",
"node-mocks-http": "^1.7.3",
"nodemon": "^1.18.6",
"nyc": "^13.1.0",
"passport": "^0.4.0",
Expand Down
99 changes: 99 additions & 0 deletions server/controllers/FollowController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import models from '../models';
import errorResponse from '../helpers/errorResponse';
import getFollowViews from '../helpers/getfollowViews';

const { User, Article, Follow } = models;

/**
* @class FollowController
* @description Follow related Operations
*/
class FollowController {
/**
* @description
* @param {object} req - request object
* @param {object} res - response object
* @returns {object} - return success repsonse
*/
static followAuthor(req, res) {
const { authorId } = req.params;
const { id } = req.userData;
const successResponse = (message, followStatus) => res.status(200).json({
status: 'success',
message,
followStatus
});
User.findOne({
where: { id: authorId },
include: [{
model: Article,
as: 'publishedArticles',
}]
})
.then((author) => {
switch (true) {
case (!author): {
return errorResponse('', res, 'user not found', 404);
}
case (+author.id === +id): {
return errorResponse('', res, 'you cant follow yourself', 403);
}
case (author.publishedArticles.length < 5): {
return errorResponse('', res, 'you cant follow this user', 403);
}
case (author.publishedArticles.length >= 5):
Follow.findOrCreate({
where: { authorId, followerId: id }
})
.spread((followed, status) => {
if (status) {
return successResponse('you followed this author', true);
}
Follow.destroy({
where: {
authorId, followerId: id
}
})
.then((unfollowed) => {
if (unfollowed) {
return successResponse(
'you unfollowed this author', false
);
}
})
.catch(err => errorResponse(err, res));
})
.catch(err => errorResponse(err, res));
break;
default:
}
})
.catch(error => errorResponse(error, res));
}

/**
* @description
* @param {object} req - request object
* @param {object} res - response object
* @returns {object} - return success repsonse
*/
static displayFollowView(req, res) {
const { userId } = req.params;
getFollowViews('authorId', userId, 'follower')
.then((followerView) => {
getFollowViews('followerId', userId, 'following')
.then((followingView) => {
const following = followingView.map(x => x.following);
const followers = followerView.map(x => x.follower);
return res.status(200).json({
status: 'success',
following,
followers,
});
})
.catch(err => errorResponse(err, res));
});
}
}

export default FollowController;
18 changes: 18 additions & 0 deletions server/helpers/errorResponse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* This arrow function reformats errors.
* @param {string} err - the error object
* @param {string} res - the response object
* @param {string} message - error message
* @param {string} statusCode - the status code
* @returns {object} return email if it exist or null if it doesn't.
*/
const errorResponse = (err, res, message, statusCode = '') => {
res.status(statusCode || 500).json({
status: 'failure',
errors: {
message: [message || err.message]
}
});
};

export default errorResponse;
17 changes: 17 additions & 0 deletions server/helpers/getfollowViews.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import models from '../models';

const { Follow, User } = models;

const getFollowViews = (queryParam, userId, view) => Follow.findAll({
where: {
[`${queryParam}`]: userId
},
attributes: [],
include: [{
model: User,
as: view,
attributes: ['id', 'fullName', 'avatarUrl']
}]
});

export default getFollowViews;
29 changes: 29 additions & 0 deletions server/middlewares/UserValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,35 @@ class UserValidation {
req.checkBody(fieldName, `invalid ${fieldName}`).isURL();
}

/**
* @description - This method validates the params in url
* @param {object} req - The request object
* @param {object} fieldName - The url param
* @returns {null} - returns nothing
* @memberOf UserValidation
* @static
*/
static validateUrlParams(req, fieldName) {
req.checkParams(fieldName, `invalid ${fieldName} in url`)
.isNumeric();
}

/**
* @description - This method validates the params in url
* @param {object} req - The request object
* @param {object} res - The request object
* @param {function} next - callback to the next middleware
* @param {object} fieldName - The url param
* @returns {null} - returns nothing
* @memberOf UserValidation
* @static
*/
static validateFollowUserUrl(req, res, next) {
if (req.params.authorId) UserValidation.validateUrlParams(req, 'authorId');
if (req.params.userId) UserValidation.validateUrlParams(req, 'userId');
UserValidation.sendFormattedError(req, res, next);
}

/**
* @description - This method validates the bio
* @param {object} req - The request object
Expand Down
37 changes: 37 additions & 0 deletions server/migrations/20181112154102-create-follow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export default {
up: (queryInterface, Sequelize) => {
queryInterface.createTable('Follows', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
authorId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
followerId: {
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'Users',
key: 'id'
}
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: queryInterface => queryInterface.dropTable('Follows')
};
26 changes: 26 additions & 0 deletions server/models/follow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default (sequelize, DataTypes) => {
const Follow = sequelize.define('Follow', {
authorId: {
allowNull: false,
type: DataTypes.INTEGER,
},
followerId: {
allowNull: false,
type: DataTypes.INTEGER
},
}, {});
Follow.associate = (models) => {
const { User } = models;
Follow.belongsTo(User, {
as: 'following',
foreignKey: 'authorId',
onDelete: 'CASCADE'
});
Follow.belongsTo(User, {
as: 'follower',
foreignKey: 'followerId',
onDelete: 'CASCADE'
});
};
return Follow;
};
14 changes: 13 additions & 1 deletion server/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,16 @@ export default (sequelize, DataTypes) => {
Role,
Comment,
Rating,
CommentLike
CommentLike,
Follow
} = models;

User.belongsTo(Role, {
foreignKey: 'roleId'
});

User.hasMany(Article, {
as: 'publishedArticles',
foreignKey: 'userId'
});

Expand All @@ -83,6 +85,16 @@ export default (sequelize, DataTypes) => {
User.hasMany(CommentLike, {
foreignKey: 'userId'
});
User.belongsToMany(User, {
as: 'authorId',
through: Follow,
foreignKey: 'authorId'
});
User.belongsToMany(User, {
as: 'followerId',
through: Follow,
foreignKey: 'followerId'
});
};

return User;
Expand Down
14 changes: 14 additions & 0 deletions server/routes/api/users.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from 'express';

import FollowController from '../../controllers/FollowController';
import UserValidation from '../../middlewares/UserValidation';
import UserController from '../../controllers/UsersController';
import facebookPassportRoutes from '../../config/facebookPassportRoutes';
Expand All @@ -13,6 +14,7 @@ const {
checkExistingEmail,
validateUserLogin,
validateUserUpdate,
validateFollowUserUrl
} = UserValidation;
const {
userLogin,
Expand All @@ -21,6 +23,10 @@ const {
updateProfile,
getUserProfiles,
} = UserController;
const {
followAuthor,
displayFollowView
} = FollowController;

const router = express.Router();

Expand Down Expand Up @@ -73,4 +79,12 @@ router.get('/auth/google/callback', googlePassportRoutes.callback());
// get all user profiles
router.get('/users', verifyToken, confirmUser, getUserProfiles);

router.post('/users/follow/:authorId',
validateFollowUserUrl, verifyToken, followAuthor);

router.get(
'/users/follow/:userId',
validateFollowUserUrl, displayFollowView
);

export default router;
2 changes: 1 addition & 1 deletion test/server/controllers/ArticleController.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ describe('Articles Controller Tests', () => {
.end((err, res) => {
should.equal(res.body.articles[0].title, 'Valinor');
should.equal(res.body.articles[0]
.slug, 'team-valinor');
.slug, 'team-valinore');
should.equal(res.body.articles[0]
.description, 'Team valinor is a simulation team');
should.equal(res.body.articles[0].author.fullName, 'John Mike');
Expand Down
Loading

0 comments on commit 7eff883

Please sign in to comment.