Skip to content

Commit

Permalink
Merge pull request #27 from andela/ft-user-can-fetch-followers/follow…
Browse files Browse the repository at this point in the history
…ing-167250125

#167250125 Fetch all followers and followings
  • Loading branch information
tolumide-ng committed Jul 15, 2019
2 parents 89bc7f6 + 079cc07 commit 9b5493e
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 26 deletions.
51 changes: 51 additions & 0 deletions src/controllers/user.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,57 @@ class UserController {
}
}

/**
* @static
* @param {object} req - express request object
* @param {object} res - express response object
* @returns {object}- returns information about the follow status of the requested user
* @memberof UserController
*/
static async getFollowers(req, res) {
const { id: followeeId } = req.currentUser;
// const followers = await BaseRepository.findAll(db.Follower, { followeeId });
const followers = await BaseRepository.findAndInclude(
db.Follower,
{ followeeId },
db.User,
'followed'
);
if (followers.length > 1) {
return responseGenerator.sendSuccess(res, 200, followers);
}
return responseGenerator.sendError(
res,
200,
`You do not have any followers at the moment`
);
}

/**
* @static
* @param {object} req - express request object
* @param {object} res - express response object
* @returns {object}- returns information about the follow status of the requested user
* @memberof UserController
*/
static async getFollowings(req, res) {
const { id: followerId } = req.currentUser;
const followings = await BaseRepository.findAndInclude(
db.Follower,
{ followerId },
db.User,
'followed'
);
if (followings.length > 1) {
return responseGenerator.sendSuccess(res, 200, followings);
}
return responseGenerator.sendError(
res,
200,
`You are not following anyone at the moment`
);
}

/**
* Get users and their corresponding profiles
* @async
Expand Down
13 changes: 11 additions & 2 deletions src/database/models/follower.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,17 @@ module.exports = (sequelize, DataTypes) => {
},
{}
);
Follower.associate = (/* models */) => {
// associations can be defined here
Follower.associate = models => {
Follower.belongsTo(models.User, {
foreignKey: 'followerId',
as: 'follower'
});
};
Follower.associate = models => {
Follower.belongsTo(models.User, {
foreignKey: 'followeeId',
as: 'followed'
});
};
return Follower;
};
6 changes: 4 additions & 2 deletions src/helpers/passport/callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ export const callback = async (accessToken, refreshToken, payload, done) => {
const theUser = await user.get({ plain: true });
const { id, username, email, role, status } = theUser;
const data = { id, username, email, role, status };
const token = helpers.jwtSigner({ data });
const token = helpers.jwtSigner({ ...data });

const info = {
id,
username,
Expand All @@ -32,7 +33,8 @@ export const callback = async (accessToken, refreshToken, payload, done) => {
token,
created
};
done(null, info);

return done(null, info);
} catch (err) {
throw new Error(err);
}
Expand Down
5 changes: 3 additions & 2 deletions src/helpers/passport/facebook.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import passport from 'passport';
import FacebookStrategy from 'passport-facebook';
import dotenv from 'dotenv';
import { callback } from './callback';
import { callback, respondCallback } from './callback';

dotenv.config();

Expand All @@ -14,6 +14,7 @@ passport.use(
profileFields: ['id', 'displayName', 'photos', 'emails', 'name'],
enableProof: false
},
callback
callback,
respondCallback
)
);
6 changes: 3 additions & 3 deletions src/helpers/passport/github.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import passport from 'passport';
import dotenv from 'dotenv';
import GitHubStrategy from 'passport-github2';
import db from '../../database/models';
import { callback } from './callback';
import { callback, respondCallback } from './callback';

dotenv.config();

Expand All @@ -14,6 +13,7 @@ passport.use(
callbackURL: 'http://localhost:3000/auth/github/callback',
scope: 'user:email'
},
callback
callback,
respondCallback
)
);
5 changes: 3 additions & 2 deletions src/helpers/passport/google.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import passport from 'passport';
import GoogleStrategy from 'passport-google-oauth20';
import dotenv from 'dotenv';
import { callback } from './callback';
import { callback, respondCallback } from './callback';

dotenv.config();

Expand All @@ -12,6 +12,7 @@ passport.use(
clientID: process.env.google_clientID,
clientSecret: process.env.google_clientSecret
},
callback
callback,
respondCallback
)
);
29 changes: 29 additions & 0 deletions src/repository/base.repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,35 @@ class BaseRepository {
static async findAndCountAll(model, options) {
return model.findAndCountAll({ ...options });
}

/**
*
*
* @static
* @param {object} model - database model
* @param {object} options - column options
* @param {object} associatedModel - associated database model
* @param {string} alias - title of the alias
* @param {object} associatedOptions - query for the associated model
* @returns {object} - returns a database object
* @memberof BaseRepository
*/
static async findAndInclude(
model,
options,
associatedModel,
alias,
associatedOptions
) {
return model.findAll({
where: options,
include: {
model: associatedModel,
as: alias,
where: associatedOptions
}
});
}
}

export default BaseRepository;
18 changes: 3 additions & 15 deletions src/routes/v1/auth.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ router.get(

router.get(
'/google/redirect',
passport.authenticate(
'google',
{ session: false },
{ failureRedirect: '/fail' }
),
passport.authenticate('google', { session: false }),
respondCallback
);

Expand All @@ -35,11 +31,7 @@ router.get(

router.get(
'/github/callback',
passport.authenticate(
'github',
{ session: false },
{ failureRedirect: '/fail' }
),
passport.authenticate('github', { session: false }),
respondCallback
);

Expand All @@ -52,11 +44,7 @@ router.get(

router.get(
'/facebook/callback',
passport.authenticate(
'facebook',
{ session: false },
{ failureRedirect: '/fail' }
),
passport.authenticate('facebook', { session: false }),
respondCallback
);

Expand Down
14 changes: 14 additions & 0 deletions src/routes/v1/user.route.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,18 @@ router.patch(
UserController.followUser
);

router.get(
'/followers',
authMiddleware,
paginationValidations,
UserController.getFollowers
);

router.get(
'/following',
authMiddleware,
paginationValidations,
UserController.getFollowings
);

export default router;
128 changes: 128 additions & 0 deletions src/test/user.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ it('should return error if database error occurs', done => {
describe('PATCH api/v1/users/follow', () => {
beforeEach(async () => {
await db.User.destroy({ cascade: true, truncate: true });
await db.Follower.destroy({ cascade: true, truncate: true });
});

it('should follow another user', async () => {
Expand Down Expand Up @@ -375,6 +376,7 @@ describe('PATCH api/v1/users/follow', () => {
describe('PATCH /api/v1/unfollow', () => {
beforeEach(async () => {
await db.User.destroy({ cascade: true, truncate: true });
await db.Follower.destroy({ cascade: true, truncate: true });
});

it('should unfollow a user', async () => {
Expand Down Expand Up @@ -456,3 +458,129 @@ describe('PATCH /api/v1/unfollow', () => {
expect(currentNumberOfFollowing.length).to.equal(0);
});
});

describe('GET /api/v1/followers', () => {
beforeEach(async () => {
await db.User.destroy({ cascade: true, truncate: true });
await db.Follower.destroy({ cascade: true, truncate: true });
});

it('should get all followers', async () => {
const firstUser = await createUser();
const secondUser = await createUser();

const firstToken = helper.jwtSigner(firstUser);

const numberOfFollowers = await BaseRepository.findAll(db.Follower, {
followeeId: secondUser.id
});

expect(numberOfFollowers.length).to.equal(0);

await server()
.patch(`${USER_API}/follow`)
.set('token', firstToken)
.send({ followeeId: secondUser.id });

const newNumberOfFollowers = await BaseRepository.findAll(db.Follower, {
followeeId: secondUser.id
});
expect(newNumberOfFollowers.length).to.equal(1);
expect(newNumberOfFollowers[0].followeeId).to.equal(secondUser.id);
expect(newNumberOfFollowers[0].followerId).to.equal(firstUser.id);

const secondToken = helper.jwtSigner(secondUser);

const res = await server()
.get(`${USER_API}/followers`)
.set('token', secondToken);

expect(res.status).to.equal(200);

const currentNumberOfFollowers = await BaseRepository.findAll(db.Follower, {
followeeId: secondUser.id
});
expect(currentNumberOfFollowers.length).to.equal(1);
});

it('should return a specific message if there are no followers', async () => {
const firstUser = await createUser();

const token = helper.jwtSigner(firstUser);

const numberOfFollowers = await BaseRepository.findAll(db.Follower, {
followeeId: firstUser.id
});

expect(numberOfFollowers.length).to.equal(0);

const res = await server()
.get(`${USER_API}/followers`)
.set('token', token);

expect(res.status).to.equal(200);

const newNumberOfFollowers = await BaseRepository.findAll(db.Follower, {
followeeId: firstUser.id
});

expect(newNumberOfFollowers.length).to.equal(0);
});
});

describe('GET /api/v1/following', () => {
beforeEach(async () => {
await db.User.destroy({ cascade: true, truncate: true });
await db.Follower.destroy({ cascade: true, truncate: true });
});

it('should get all followed users', async () => {
const firstUser = await createUser();
const secondUser = await createUser();

const firstToken = helper.jwtSigner(firstUser);

const followedUsers = await BaseRepository.findAll(db.Follower, {
followerId: firstUser.id
});

expect(followedUsers.length).to.equal(0);

await server()
.get(`${USER_API}/following`)
.set('token', firstToken)
.send({ followeeId: secondUser.id });

const newFollowedUsers = await BaseRepository.findAll(db.Follower, {
followerId: firstUser.id
});
expect(newFollowedUsers.length).to.equal(0);
});

it("should return a specific message if user isn't following anyone", async () => {
const firstUser = await createUser();

const token = helper.jwtSigner(firstUser);

const numberOfFollowers = await BaseRepository.findAll(db.Follower, {
followerId: firstUser.id
});

expect(numberOfFollowers.length).to.equal(0);

const res = await server()
.get(`${USER_API}/following`)
.set('token', token);

expect(res.status).to.equal(200);
expect(res.body.message).to.equal(
`You are not following anyone at the moment`
);

const newNumberOfFollowers = await BaseRepository.findAll(db.Follower, {
followerId: firstUser.id
});

expect(newNumberOfFollowers.length).to.equal(0);
});
});
Loading

0 comments on commit 9b5493e

Please sign in to comment.