Skip to content

Commit

Permalink
Merge 28b1f83 into cecca76
Browse files Browse the repository at this point in the history
  • Loading branch information
devdbrandy committed Apr 28, 2019
2 parents cecca76 + 28b1f83 commit 8905fec
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 71 deletions.
47 changes: 34 additions & 13 deletions server/controllers/followersController.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import createError from 'http-errors';
import models from '../models';
import Response from '../helpers/responseHelper';
import { MESSAGE } from '../helpers/constants';
import { STATUS, MESSAGE } from '../helpers/constants';

/**
* Class handling followers operation
Expand All @@ -20,12 +20,18 @@ class FollowersController {
* @memberof FollowersController
*/
static async follow(request, response, next) {
const { user, params: { username } } = request;
const { user } = request;
const { profile: { username } } = response.locals;

try {
const { followable, follower } = await FollowersController.validateFollowable(user, username);
await followable.addFollower(follower);
return Response.send(response, 200, followable, `${MESSAGE.FOLLOW_SUCCESS} ${username}`);
return Response.send(
response,
STATUS.OK,
followable,
`${MESSAGE.FOLLOW_SUCCESS} ${username}`
);
} catch (error) {
next(error);
}
Expand All @@ -42,17 +48,23 @@ class FollowersController {
* @memberof FollowersController
*/
static async unfollow(request, response, next) {
const { user, params: { username } } = request;
const { user } = request;
const { profile: { username } } = response.locals;

try {
const { followable, follower } = await FollowersController.validateFollowable(user, username);
const existingFollower = await followable.hasFollowers(follower);
if (!existingFollower) {
next(createError(400, MESSAGE.UNFOLLOW_ERROR));
next(createError(STATUS.BAD_REQUEST, MESSAGE.UNFOLLOW_ERROR));
}

await followable.removeFollower(follower);
return Response.send(response, 200, followable, `${MESSAGE.UNFOLLOW_SUCCESS} ${username}`);
return Response.send(
response,
STATUS.OK,
followable,
`${MESSAGE.UNFOLLOW_SUCCESS} ${username}`
);
} catch (error) {
next(error);
}
Expand All @@ -70,16 +82,17 @@ class FollowersController {
static async validateFollowable(user, username) {
try {
const follower = await models.User.findOne({
where: { id: user.id }
where: { id: user.id },
attributes: ['id', 'email']
});
const profile = await models.Profile.findOne({ where: { username } });
if (follower.id === profile.userId) {
throw createError(400, MESSAGE.FOLLOW_ERROR);
throw createError(STATUS.BAD_REQUEST, MESSAGE.FOLLOW_ERROR);
}

const followable = await profile.getUser();
const followable = await profile.getUser({ attributes: ['id', 'email', 'deletedAt'] });
if (followable.deletedAt !== null) {
throw createError(404, MESSAGE.FOLLOW_ERROR);
throw createError(STATUS.NOT_FOUND, MESSAGE.INACTIVE_ACCOUNT);
}

return { followable, follower };
Expand Down Expand Up @@ -108,13 +121,21 @@ class FollowersController {
where: { id: user.id }
});

const queryOptions = {
attributes: ['id', 'email'],
include: [{
model: models.Profile,
attributes: ['firstname', 'lastname', 'username']
}],
joinTableAttributes: [],
};
if (routePath === 'followers') {
followers = await authUser.getFollowers();
followers = await authUser.getFollowers(queryOptions);
} else {
followers = await authUser.getFollowing();
followers = await authUser.getFollowing(queryOptions);
}

return Response.send(response, 400, followers);
return Response.send(response, STATUS.OK, followers);
} catch (error) {
next(error);
}
Expand Down
2 changes: 2 additions & 0 deletions server/helpers/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export const MESSAGE = {
SUCCESS_MESSAGE: 'Operation was successful',

CLAP_FORBIDDEN: 'Sorry you cannot clap an article created by you',

INACTIVE_ACCOUNT: 'User account has been deactivated',
};

export const TOKEN_VALIDITY = 604800; // 7 days
Expand Down
29 changes: 29 additions & 0 deletions server/middlewares/validator.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { body, query, param } from 'express-validator/check';
import createError from 'http-errors';
import {
STATUS,
MESSAGE,
FIELD,
PAGE_LIMIT,
CLAPS_LIMIT
} from '../helpers/constants';
import UsersController from '../controllers/usersController';
import ProfileController from '../controllers/profileController';
import models from '../models';

/**
* Used with express validator to validate input paramters
* @export
Expand Down Expand Up @@ -376,4 +380,29 @@ export default class Validator {
.withMessage('User Id must be a valid integer')
];
}

/**
* Validate profile resource by username
*
* @static
* @param {object} request - Express Request object
* @param {object} response - Express Response object
* @param {NextFunction} next - Express nextFunction
* @returns {Function} call to next middleware
* @memberof AriclesMiddleware
*/
static async validateUsernameResource(request, response, next) {
const { params: { username } } = request;

try {
const profile = await models.Profile.findOne({ where: { username } });
if (!profile) {
throw createError(STATUS.NOT_FOUND, MESSAGE.RESOURCE_NOT_FOUND);
}
response.locals.profile = profile;
return next();
} catch (error) {
return next(error);
}
}
}
78 changes: 40 additions & 38 deletions server/routes/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,44 @@ profileRouter.post(
profileController.updateProfile,
)

/**
* @swagger
* /api/v1/profiles/followers:
* get:
* tags:
* - followers
* description: Fetch all followers for authenticated user
* produces:
* - application/json
* responses:
* 200:
* description: Successful
*/
.get(
'/profiles/followers',
middlewares.authenticate,
followersController.followers
)

/**
* @swagger
* /api/v1/profiles/followers:
* get:
* tags:
* - followers
* description: Fetch all the following users by authenticated user
* produces:
* - application/json
* responses:
* 200:
* description: Successful
*/
.get(
'/profiles/following',
middlewares.authenticate,
followersController.followers
)

/**
* @swagger
* /api/v1/profile:
Expand Down Expand Up @@ -141,6 +179,7 @@ profileRouter.post(
profileRouter.post(
'/profiles/:username/follow',
middlewares.authenticate,
Validator.validateUsernameResource,
followersController.follow
);

Expand All @@ -162,45 +201,8 @@ profileRouter.post(
profileRouter.post(
'/profiles/:username/unfollow',
middlewares.authenticate,
Validator.validateUsernameResource,
followersController.unfollow
);

/**
* @swagger
* /api/v1/profiles/followers:
* get:
* tags:
* - followers
* description: Fetch all followers by authenticated user
* produces:
* - application/json
* responses:
* 200:
* description: Successful
*/
profileRouter.get(
'/profiles/followers',
middlewares.authenticate,
followersController.followers
);

/**
* @swagger
* /api/v1/profiles/followers:
* get:
* tags:
* - followers
* description: Fetch all the following users by authenticated user
* produces:
* - application/json
* responses:
* 200:
* description: Successful
*/
profileRouter.get(
'/profiles/following',
middlewares.authenticate,
followersController.followers
);

export default profileRouter;
117 changes: 97 additions & 20 deletions test/integrations/routes/profile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import app from '../../../server';
import { STATUS, MESSAGE } from '../../../server/helpers/constants';
import models from '../../../server/models';
import { users } from '../../helpers/testData';
import { auth } from '../../helpers';

const { dummyUser2 } = users;

Expand Down Expand Up @@ -144,28 +145,104 @@ describe('Testing user profile feature', () => {
});
});
});
describe('POST /api/v1/profile/:username/follow', async () => {
describe('POST /api/v1/profiles/:username/follow', async () => {
let user;
let userToken;
let follower;
let followerToken;
let followerUsername;

before(async () => {
let result;
result = await models.User.findOne({
include: [models.Profile]
});
user = result.get({ plain: true });
user.password = 'secret';
let response = await auth(user);
userToken = response.body.token;

result = await models.User.findByPk(2, {
include: [models.Profile]
});
follower = result.get({ plain: true });
follower.password = 'secret';
response = await auth(follower);
followerToken = response.body.token;
followerUsername = follower.Profile.username;
});

it('should allow a user to follow another user', (done) => {
// let token;
models.User
.create({
email: 'faker37@email.com',
password: 'secret12345',
})
.then((user) => {
profile.userId = user.id;
profile.username = 'johnnybravo';
return models.Profile.create(profile);
})
.then(({ username }) => (
chai.request(app)
.post(`/api/v1/profiles/${username}/follow`)
.set('Authorization', `Bearer ${dummyUser3.token}`)
))
.then((res) => {
chai
.request(app)
.post(`/api/v1/profiles/${followerUsername}/follow`)
.set({ Authorization: `Bearer ${userToken}` })
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(STATUS.OK);
expect(res.body)
.to.haveOwnProperty('message')
.to.equal(`Successfully followed ${followerUsername}`);
done();
})
.catch(done);
});
});
it('should get a list of user (followedUser) followers', (done) => {
chai
.request(app)
.get('/api/v1/profiles/followers')
.set({ Authorization: `Bearer ${followerToken}` })
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(STATUS.OK);
expect(res.body)
.to.haveOwnProperty('data')
.to.be.an('array')
.lengthOf(1);
done();
});
});
it('should get a list of user following', (done) => {
chai
.request(app)
.get('/api/v1/profiles/following')
.set({ Authorization: `Bearer ${userToken}` })
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(STATUS.OK);
expect(res.body)
.to.haveOwnProperty('data')
.to.be.an('array')
.lengthOf(1);
done();
});
});
it('should allow a user to unfollow user', (done) => {
chai
.request(app)
.post(`/api/v1/profiles/${followerUsername}/unfollow`)
.set({ Authorization: `Bearer ${userToken}` })
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(STATUS.OK);
expect(res.body)
.to.haveOwnProperty('message')
.to.equal(`Successfully unfollowed ${followerUsername}`);
done();
});
});
it('should not allow a user to follow self', (done) => {
const { username } = user.Profile;
chai
.request(app)
.post(`/api/v1/profiles/${username}/follow`)
.set({ Authorization: `Bearer ${userToken}` })
.end((err, res) => {
expect(err).to.be.null;
expect(res).to.have.status(STATUS.BAD_REQUEST);
expect(res.body)
.to.haveOwnProperty('message')
.to.equal(MESSAGE.FOLLOW_ERROR);
done();
});
});
});

0 comments on commit 8905fec

Please sign in to comment.