Skip to content

Commit

Permalink
Merge 120476f into 3a2eb25
Browse files Browse the repository at this point in the history
  • Loading branch information
Hasstrup committed Jul 15, 2018
2 parents 3a2eb25 + 120476f commit 9c05acf
Show file tree
Hide file tree
Showing 28 changed files with 4,554 additions and 3,439 deletions.
2 changes: 1 addition & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
/public/**
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ dist
coverage/
.nyc_output/
bower_components
.eslintrc.js
3 changes: 2 additions & 1 deletion app/controllers/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ const gameResult = (req, res) => {
const game = new Game(payload);
game.save((err) => {
if (err) {
return res.status(500).json({ message: 'Error saving game' });
const message = err.message || 'Error saving game';
return res.status(500).json({ message });
}
return res.status(201).json(game);
});
Expand Down
27 changes: 24 additions & 3 deletions app/controllers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import mongoose from 'mongoose';
import passport from 'passport';
import avatarsList from './avatars';
import { Tokenizer } from '../helpers/tokenizer';
import sendInvitationEmail from '../helpers/sendInvitationEmail';
import sendInvite from '../helpers/sendInvitationEmail';
import Services from '../logic/user';

const { handleFetchProfile } = Services;

const avatarsArray = avatarsList.all();
const User = mongoose.model('User');
Expand Down Expand Up @@ -47,7 +50,8 @@ const signin = (req, res) => {
* @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 '/api/auth/login', returns token in response as JSON.
* @description Controller for handling requests to '/api/auth/login',
* returns token in response as JSON.
* @param {object} passport - passport with all the startegies registered
* @description Controller for handling requests to '/api/auth/login',
* returns token in response as JSON.
Expand Down Expand Up @@ -107,6 +111,21 @@ 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
* has been decoded and payload appended to the request object
*/
const fetchProfile = (req, res, next) => handleFetchProfile(req.user._id)
.then((data) => {
res.status(200).json({ data });
})
.catch((err) => {
next(err);
});


/**
* @param {object} req - request object provided by express
Expand Down Expand Up @@ -230,6 +249,7 @@ const addDonation = (req, res) => {
.exec((err, user) => {
// Confirm that this object hasn't already been entered
let duplicate = false;
/* eslint no-plusplus: 0 */
for (let i = 0; i < user.donations.length; i++) {
if (user.donations[i].crowdrise_donation_id === req.body.crowdrise_donation_id) {
duplicate = true;
Expand Down Expand Up @@ -320,7 +340,7 @@ const findUsers = (req, res) => {
*/
const invite = (req, res) => {
const recipient = req.body.user;
if (sendInvitationEmail(recipient, req.body.link)) {
if (sendInvite(recipient, req.body.link)) {
return res.status(200).send({ user: recipient });
}
return res.status(400).send({ message: 'An error occurred while sending the invitation' });
Expand All @@ -339,6 +359,7 @@ export default {
signup,
signin,
handleLogin,
fetchProfile,
handleSignUp,
avatars,
findUsers,
Expand Down
60 changes: 60 additions & 0 deletions app/logic/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @module Services this module is created to wrap the
* core logic of all the controllers so that the logic is
* unit testable
*/

import mongoose from 'mongoose';

const User = mongoose.model('User');
const Game = mongoose.model('Game');

/**
* @name returnGamesWon
* @type {function}
* @param {array} games The games that the requesting user has played
* @param {object} user The current user
* @description this method sorts through the games provided and
* checks where the current user has won, uses .toString() because direct _id
* comparison seems buggy and anomalous
* @returns {object} The user object and the games played;
*/
function returnGamesWon(games, user) {
return games.filter(game => game.gameWinner._id.toString() === user._id.toString());
}


/**
* @name handleFetchProfile
* @type {function}
* @param {string} _id The user id, should be a mongoose ObjectID type (usually a string)
* @description this method check for a user and all the games he/she has played,
* populating the players and the winner in the process.;
* @returns {object} The user object and the games played;
*/
const handleFetchProfile = _id => new Promise((resolve, reject) => {
User.findOne({ _id }, (err, user) => {
if (err) reject(err);
if (user) {
return Game.find({
players: user._id
})
.populate('players')
.populate('gameWinner')
.exec((err, games) => {
if (err) return reject(err);
// using toString() because these methods behave anomalously
const gamesWon = returnGamesWon(games, user);
return resolve({ ...user._doc, games, gamesWon });
});
}
const error = new Error('There is no user matching the query');
error.status = 403;
reject(error);
});
});


export default {
handleFetchProfile
};
5 changes: 4 additions & 1 deletion app/middleware/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ const auth = (req, res, next) => {
token = token.replace('Bearer ', '');

if (!token) {
return res.status(401).json({ message: 'Unauthorized Access' });
const err = new Error('You need to be authorized to access this route');
err.status = 403;
return next(err);
}

jwt.verify(token, process.env.SECRET_KEY, (err, result) => {
if (err) {
return res.status(401).json({ message: 'Please login!' });
}
req.decoded = result;
req.user = result;
next();
});
};
Expand Down
18 changes: 9 additions & 9 deletions app/models/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,20 @@ const { Schema } = mongoose;
* Game Schema
*/
const GameSchema = new Schema({
id: {
type: Number
},
gameId: {
type: Number
},
gameWinner: {
type: String,
default: '',
trim: true
type: Schema.Types.ObjectId,
trim: true,
ref: 'User'
},
players: {
type: [String]
}
players: [{
type: Schema.Types.ObjectId,
ref: 'User'
}]
}, {
timestamps: true
});

GameSchema.statics = {
Expand Down
6 changes: 6 additions & 0 deletions app/views/includes/foot.jade
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ script(type='text/javascript', src='https://code.angularjs.org/1.1.5/angular.min
script(type='text/javascript', src='https://code.angularjs.org/1.1.5/angular-resource.min.js')
script(type='text/javascript', src='https://code.angularjs.org/1.1.5/angular-cookies.min.js')

// MomentJS
script(type='text/javascript', src='https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js')

//Angular UI
script(type='text/javascript', src='/lib/angular-bootstrap/ui-bootstrap-tpls.js')
script(type='text/javascript', src='/lib/angular-ui-utils/modules/route.js')
Expand All @@ -38,6 +41,9 @@ script(type='text/javascript', src='/js/controllers/auth.js')
script(type='text/javascript', src='/js/controllers/invitePlayers.js')
script(type='text/javascript', src='/js/init.js')

// Application components
script(type='text/javascript', src='/js/directives/profile.js')


//Socket.io Client Library
script(type='text/javascript', src='/socket.io/socket.io.js')
Expand Down
1 change: 1 addition & 0 deletions app/views/includes/head.jade
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ head
link(rel='stylesheet', href='/css/animate.css')
link(rel='stylesheet', href='/css/style.css')
link(rel='stylesheet', href='/css/find-users.css')
link(rel='stylesheet', href='/css/profile.css')

//if lt IE 9
script(src='https://html5shim.googlecode.com/svn/trunk/html5.js')
66 changes: 0 additions & 66 deletions backend-test/integration/auth.test.js

This file was deleted.

94 changes: 94 additions & 0 deletions backend-test/integration/user.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import 'babel-polyfill';
import request from 'supertest';
import faker from 'faker';
import { expect } from 'chai';
import mongoose from 'mongoose';
import app from '../../server';
import { Tokenizer } from '../../app/helpers/tokenizer';

mongoose.Promise = global.Promise;

const User = mongoose.model('User');
let token;

const mock = {
name: 'Hasstrup Ezekiel',
password: '12345',
username: 'hasstrupezekiel',
email: 'hasstrup@email.com'
};

describe('User endpoints', () => {
describe('Authentication', () => {
before(() => Promise.resolve(User.create(mock).then((user) => {
token = Tokenizer(user);
})
.catch((err) => {
throw err;
})));

it('POST /api/auth/login should return the user token along with the', (done) => {
try {
request(app)
.post('/api/auth/login')
.send(mock)
.end((err, res) => {
if (err) return done(err);
expect(res.statusCode).to.equal(200);
expect(res.body.token).to.equal(Tokenizer(res.body));
done();
});
} catch (err) {
/* eslint no-unused-expressions: 0 */
expect(err).to.not.exist;
}
});

it('POST /api/auth/signup should return the token of a user on sign up', (done) => {
try {
const fake = {
username: faker.internet.userName(),
name: faker.name.findName(),
email: faker.internet.email(),
password: faker.internet.password(),
};

request(app)
.post('/api/auth/signup')
.send(fake)
.end((err, res) => {
if (err) return done(err);
expect(res.statusCode).to.equal(201);
expect(res.body.token).to.equal(Tokenizer(res.body));
done();
});
} catch (err) {
expect(err).to.not.exist;
}
});
});
describe('Show user profile', () => {
it(' GET /api/profile Should return the profile of the user showing the games played(if any)', (done) => {
request(app)
.get('/api/profile')
.set('Authorization', token)
.end((err, res) => {
if (err) return done(err);
expect(res.statusCode).to.equal(200);
expect(res.body.data.name).to.equal('Hasstrup Ezekiel');
expect(res.body.data.games).to.be.an('array');
done();
});
});

it('GET /api/profile shoulfd fail without a token with a 403 error', (done) => {
request(app)
.get('/api/profile')
.end((err, res) => {
if (err) return done(err);
expect(res.statusCode).to.equal(404);
done();
});
});
});
});
Loading

0 comments on commit 9c05acf

Please sign in to comment.