Skip to content

Commit

Permalink
Merge pull request #35 from andela/invite-players-and-connet-as-frien…
Browse files Browse the repository at this point in the history
…ds-#158457046

158457046 Send friend request and notification popup
  • Loading branch information
Onah Benjamin Ifeanyi committed Oct 16, 2018
2 parents 94754af + 292d628 commit 859d926
Show file tree
Hide file tree
Showing 25 changed files with 1,140 additions and 256 deletions.
10 changes: 10 additions & 0 deletions app.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
{
"name": "ET-cfh",
"scripts": {
"postdeploy": "heroku config:set HOST_NAME=${HEROKU_APP_NAME}.herokuapp.com --app ${HEROKU_APP_NAME}"
},
"env": {
"HEROKU_APP_NAME": {
"required": true
},
"HEROKU_PARENT_APP_NAME": {
"required": true
},
"MONGODB_URI": {
"required": true
},
Expand Down Expand Up @@ -61,6 +68,9 @@
"mongolab"
],
"buildpacks": [
{
"url": "https://github.com/heroku/heroku-buildpack-cli"
},
{
"url": "heroku/nodejs"
}
Expand Down
179 changes: 169 additions & 10 deletions app/controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,53 @@ import Services from '../logic/user';

const { handleFetchProfile } = Services;


const avatarsArray = avatarsList();
const User = mongoose.model('User');

/**
* @param {user} user the user whose friend list and
* friend request list will be searched
* @param {*} id the id of the finder
* @description this method verifies the friendship status between a
* finder and the user that is found. Friendship status
* can be friends, not friends, or pending
*/
const checkIfFriends = (user, id) => {
let friends = 'not friends';
friends = user.friend_requests.indexOf(mongoose.Types.ObjectId(id)) > -1
? 'pending' : friends;
friends = user.friends.indexOf(mongoose.Types.ObjectId(id)) > -1
? 'friends' : friends;
return friends;
};

/**
* @param {users} users the list of all returned users
* @description this function takes a list of users from the database,
* removes the sensitive fields and returns the list of users
*/
const cleanUpUsers = (users = [], _id) => users.map(user => ({
_id: user._id,
name: user.name,
email: user.email,
avatar: user.avatar
|| 'https://assets.hotukdeals.com/assets/img/profile-placeholder_f56af.png',
friends: checkIfFriends(user, _id),
}));

/**
* @param {users} users the list of all returned users
* @description this function takes a list of users from the database,
* removes the sensitive fields and returns the list of users
*/
const cleanUpFriends = users => users.map(user => ({
_id: user._id,
name: user.name,
email: user.email,
avatar: user.avatar
|| 'https://assets.hotukdeals.com/assets/img/profile-placeholder_f56af.png',
}));

// disabling no underscore because of the default style of mongoose ids
/* eslint no-underscore-dangle: 0, valid-jsdoc: 0 */

Expand All @@ -38,7 +81,8 @@ const authCallback = (req, res) => {
* @param {res} res handles response status code and messages
* @returns {res} a status code and data
* @description this function is called with an Oauth 2 authentication is complete
* the authentication returns a user which info is sent to the client through the url.
* the authentication returns a user which
* info is sent to the client through the url.
*/
const signin = (req, res) => {
if (!req.user) {
Expand Down Expand Up @@ -82,7 +126,8 @@ const handleLogin = (req, res, next) => {
* @param {object} res - response object provided by express
* @param {function} next - next function for passing the request to next handler
* @description Controller for handling requests to '/api/auth/signup',
* returns the token of the user on signup, users Tokenizer to generate the token as well.
* returns the token of the user on signup,
* users Tokenizer to generate the token as well.
*/
const handleSignUp = (req, res, next) => {
// there has to be the email, username and password
Expand Down Expand Up @@ -126,7 +171,8 @@ const handleSignUp = (req, res, next) => {
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @param {function} next - next function for passing the request to next handler
* @description controller for handling requests to get the user profile, expects that a token
* @description controller for handling requests to get
* the user profile, expects that a token
* has been decoded and payload appended to the request object
*/
const fetchProfile = (req, res, next) => handleFetchProfile(req.user._id)
Expand All @@ -141,7 +187,8 @@ const fetchProfile = (req, res, next) => handleFetchProfile(req.user._id)
/**
* @param {object} req - request object from OAUTH callback
* @param {object} res - request object provided by express
* @description This action generates a token after a successful oauth login and sends the token
* @description This action generates a token after
* a successful oauth login and sends the token
* the client to be used for subsequent requests.
*/

Expand Down Expand Up @@ -235,7 +282,8 @@ const create = (req, res, next) => {
/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description controller handling uploading choosing avatars on POST '/api/users/avatars'
* @description controller handling uploading
* choosing avatars on POST '/api/users/avatars'
*/
const avatars = (req, res) => {
// Update the current user's profile to include the avatar choice they've made
Expand All @@ -256,7 +304,8 @@ const avatars = (req, res) => {
/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description controller handling the new donations request on POST '/api/donations',
* @description controller handling the new donations request
* on POST '/api/donations',
* expects that the request body contains crowdrise data, and the amount.
*/
const addDonation = (req, res) => {
Expand Down Expand Up @@ -354,8 +403,9 @@ const updateUserTour = (req, res, next) => {
* that match the key. It search the name and email only.
*/
const findUsers = (req, res) => {
const { searchKey } = req.params;
const { searchKey, _id } = req.params;
User.find({
_id: { $ne: _id },
$and:
[
{
Expand All @@ -366,7 +416,7 @@ const findUsers = (req, res) => {
]
}
]
}, (err, users) => res.status(200).send({ users }));
}, (err, users) => res.status(200).send({ users: cleanUpUsers(users, _id) }));
};


Expand All @@ -384,6 +434,110 @@ const invite = (req, res) => {
return res.status(400).send({ message: 'An error occurred while sending the invitation' });
};

/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description retrieves all friends and friend request a user has
*/
const getFriends = (req, res, next) => {
const { userId } = req.params;
User.findById(userId).populate('friends')
.populate('friend_requests').exec((error, currentUser) => {
if (error) {
return next(error);
}
return res.status(200).send({
friends: cleanUpFriends(currentUser.friends),
friendRequests: cleanUpFriends(currentUser.friend_requests)
});
});
};

/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description accepts a friend request by
* adding both parties to each other's record
*/
const acceptRequest = (req, res, next) => {
const { userId } = req.params;
const { id } = req.body;
User.update({ _id: userId },
{ $push: { friends: id }, $pull: { friend_requests: id } },
(err) => {
if (err) {
return next(err);
}
User.update({ _id: id },
{ $push: { friends: userId }, $pull: { friend_requests: userId } },
(err) => {
if (err) {
return next(err);
}
res.status(200).send({ message: 'Friend request accepted' });
});
});
};

/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description unfriend user by deleting both parties form each other's record
*/
const unfriendUser = (req, res, next) => {
const { userId } = req.params;
const { id } = req.body;
User.update({ _id: userId },
{ $pull: { friends: id } },
(err) => {
if (err) {
return next(err);
}
User.update({ _id: id },
{ $pull: { friends: userId } },
(err) => {
if (err) {
return next(err);
}
res.status(200).send({ message: 'Friend removed' });
});
});
};

/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description decline friend request by deleting request from receivers record
*/
const declineRequest = (req, res, next) => {
const { userId } = req.params;
const { id } = req.body;
User.update({ _id: userId },
{ $pull: { friend_requests: id } },
(err) => {
if (err) {
return next(err);
}
res.status(200).send({ message: 'Friend request declined' });
});
};

/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description get the number of friend requests a friend has
*/
const getRequestCount = (req, res, next) => {
const { userId } = req.params;
User.findById(userId).exec((error, currentUser) => {
if (error) {
return next(error);
}
return res.status(200).send({
length: currentUser.friend_requests.length,
});
});
};

export default {
authCallback,
Expand All @@ -402,5 +556,10 @@ export default {
handleSignUp,
avatars,
findUsers,
invite
invite,
getFriends,
acceptRequest,
unfriendUser,
declineRequest,
getRequestCount
};
2 changes: 2 additions & 0 deletions app/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const UserSchema = new Schema({
tour: { type: Boolean, default: false },
premium: Number, // null or 0 for non-donors, 1 for everyone else (for now)
donations: [],
friends: [{ type: Schema.Types.ObjectId, ref: 'User' }],
friend_requests: [{ type: Schema.Types.ObjectId, ref: 'User' }],
hashed_password: String,
facebook: {},
twitter: {},
Expand Down
2 changes: 2 additions & 0 deletions app/views/includes/foot.jade
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ script(type='text/javascript', src='/js/services/global.js')
script(type='text/javascript', src='/js/services/socket.js')
script(type='text/javascript', src='/js/services/game.js')
script(type='text/javascript', src='/js/services/dashboard.js')
script(type='text/javascript', src='/js/services/friend.js')

//Application Controllers
script(type='text/javascript', src='/js/controllers/index.js')
Expand All @@ -52,6 +53,7 @@ script(type='text/javascript', src='/js/controllers/auth.js')
script(type='text/javascript', src='/js/controllers/invitePlayers.js')
script(type='text/javascript', src='/js/controllers/tour.js')
script(type='text/javascript', src='/js/controllers/dashboard.js')
script(type='text/javascript', src='/js/controllers/friend.js')
script(type='text/javascript', src='/js/init.js')

// Application components
Expand Down
16 changes: 11 additions & 5 deletions backend-test/integration/findUsers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,15 @@ const mock = [
}
];

let _id;

describe('User endpoints', () => {
before(() => {
Promise.resolve(User.create(mock));
before((done) => {
Promise.resolve(User.create(mock)).then((users) => {
const { id } = users[0];
_id = id;
done();
});
});

after(() => {
Expand All @@ -46,19 +52,19 @@ describe('User endpoints', () => {

it('GET /api/users/findUsers/:searchKey should return statusCode 200 with 2 users', (done) => {
request(app)
.get('/api/users/findUsers/ben')
.get(`/api/users/findUsers/ben/${_id}`)
.set('Authorization', `Bearer ${token}`)
.end((err, res) => {
if (err) return done(err);
expect(res.statusCode).to.equal(200);
expect(res.body.users.length).to.equal(2);
expect(res.body.users.length).to.equal(1);
done();
});
});

it('GET /api/users/findUsers/:searchKey should return statusCode 200 with no user', (done) => {
request(app)
.get('/api/users/findUsers/nothing')
.get(`/api/users/findUsers/nothing/${_id}`)
.set('Authorization', `Bearer ${token}`)
.end((err, res) => {
if (err) return done(err);
Expand Down
6 changes: 4 additions & 2 deletions backend-test/integration/gameLog.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,10 @@ const mockGames = [
const token = Tokenizer(user);

describe('Game History', () => {
before(() => {
Promise.resolve(Game.create(mockGames));
before((done) => {
Game.create(mockGames).then(() => {
done();
});
});

after(() => {
Expand Down
9 changes: 7 additions & 2 deletions config/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ export default (router, passport, app) => {
api
.post('/auth/login', users.handleLogin)
.post('/auth/signup', users.handleSignUp)
.get('/users/findUsers/:searchKey', ensureUser, users.findUsers)
.get('/users/findUsers/:searchKey/:_id', ensureUser, users.findUsers)
.get('/users/findUsers/', ensureUser, users.findUsers)
.post('/users/invite', ensureUser, users.invite)
.get('/profile', ensureUser, users.fetchProfile)
.get('/signout', users.signout);
.get('/signout', users.signout)
.get('/user/friends/:userId', ensureUser, users.getFriends)
.put('/user/accept/:userId', ensureUser, users.acceptRequest)
.put('/user/decline/:userId', ensureUser, users.declineRequest)
.put('/user/unfriend/:userId', ensureUser, users.unfriendUser)
.get('/user/getRequestCount/:userId', ensureUser, users.getRequestCount);

// Setting up user tour api
api
Expand Down
2 changes: 1 addition & 1 deletion config/socket/player.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ function Player(socket) {
this.color = null;
}

module.exports = Player;
module.exports = Player;
Loading

0 comments on commit 859d926

Please sign in to comment.