Skip to content

Commit

Permalink
Merge c93aee0 into b3e1fe6
Browse files Browse the repository at this point in the history
  • Loading branch information
Onah Benjamin Ifeanyi committed Jul 12, 2018
2 parents b3e1fe6 + c93aee0 commit dca3675
Show file tree
Hide file tree
Showing 48 changed files with 870 additions and 436 deletions.
3 changes: 2 additions & 1 deletion .env.sample
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
MONGODB_URI=<Test database url>
MONGOHQ_URL_TEST=<Test database url>
MONGODB_URI=<Development database url>
SECRET_KEY=<Secret key for JWT>
GMAIL_PASSWORD=<password is pinned at slack channel>
25 changes: 19 additions & 6 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"root": true,
"root": true,
"extends": "airbnb-base",
"env": {
"node": true,
Expand All @@ -23,18 +23,31 @@
"curly": ["error", "multi-line"],
"import/no-unresolved": [2, { "commonjs": true }],
"no-shadow": ["error", { "allow": ["req", "res", "err"] }],
"import/no-extraneous-dependencies": ["error", {"devDependencies": true}],
"valid-jsdoc": ["error", {
"requireReturn": true,
"requireReturnType": true,
"requireParamDescription": false,
"requireReturnDescription": true
}],
"require-jsdoc": ["error", {
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true
}
"require": {
"FunctionDeclaration": true,
"MethodDefinition": true,
"ClassDeclaration": true
}
}]
},
"globals": {
"document": true,
"window": true,
"localStorage": true,
"FormData": true,
"angular": true,
"require": true,
"describe": true,
"before": true,
"it": true,
"$": true
}
}
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public/lib
.DS_store
c4h_keys.txt
.env
reports/
.nyc_output/
.coverrun
dist
.coveralls.yml
Expand Down
8 changes: 7 additions & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
"scripts": {
},
"env": {
"MONGOHQ_URL": {
"MONGODB_URI": {
"required": true
},
"GMAIL_PASSWORD": {
"required": true
},
"SECRET_KEY": {
"required": true
}
},
Expand Down
72 changes: 61 additions & 11 deletions app/controllers/users.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint no-underscore-dangle: 0 */

/**
* Module dependencies.
*/
Expand All @@ -6,30 +8,32 @@ import mongoose from 'mongoose';
import passport from 'passport';
import avatarsList from './avatars';
import { Tokenizer } from '../helpers/tokenizer';
import sendInvitationEmail from '../helpers/sendInvitationEmail';

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


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

/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description controller called after successful passport social auth strategy
*/
* Auth callback
* @param {req} req carries request payload
* @param {res} res handles response status code and messages
* @returns {res} a status code and data
*/

const authCallback = (req, res) => {
res.redirect('/chooseavatars');
};


/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description controller handling GET '/signin', to redirects to the angular route.
* Provides signin form if no user present, redirects to the main game if there is.
*/
* Show login form
* @param {req} req carries request payload
* @param {res} res handles response status code and messages
* @returns {res} a status code and data
*/
const signin = (req, res) => {
if (!req.user) {
res.redirect('/#!/signin?error=invalid');
Expand All @@ -44,6 +48,12 @@ const signin = (req, res) => {
* @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.
* @param {object} passport - passport with all the startegies registered
* @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.
* Uses Tokenizer helper method to handle generation of token
*/
const handleLogin = (req, res, next) => {
Expand Down Expand Up @@ -279,6 +289,44 @@ const user = (req, res, next, id) => {
});
};

/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description find users takes a search key and returns users
* that match the key. It search the name and email only.
*/
const findUsers = (req, res) => {
const { searchKey } = req.params;
User.find({
$and:
[
{
$or:
[
{ name: { $regex: new RegExp(`.*${searchKey}.*`, 'i') } },
{ email: { $regex: new RegExp(`.*${searchKey}.*`, 'i') } },
]
}
]
}, (err, users) => res.status(200).send({ users }));
};


/**
* @param {object} req - request object provided by express
* @param {object} res - response object provided by express
* @description invite users takes a users email, sends a
* game link to that user and returns that that user.
*/
const invite = (req, res) => {
const recipient = req.body.user;
if (sendInvitationEmail(recipient, req.body.link)) {
return res.status(200).send({ user: recipient });
}
return res.status(400).send({ message: 'An error occurred while sending the invitation' });
};


export default {
authCallback,
user,
Expand All @@ -292,5 +340,7 @@ export default {
signin,
handleLogin,
handleSignUp,
avatars
avatars,
findUsers,
invite,
};
56 changes: 56 additions & 0 deletions app/helpers/sendInvitationEmail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* eslint no-useless-escape: 0, no-console: 0 */
import nodemailer from 'nodemailer';
import IsUrl from 'is-url';

/**
* @param {string} email - is the user's email
* @returns {boolean} boolean if email verification fails
*/
const IsEmail = (email) => {
const re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
};

/**
* @param {object} user - the email of the user to be invited
* @param {string} link - the custom link for the current game
* @description this function takes a (email, link) argument and sends
* a meal bearing the game's link
* @returns {null} returns nothing
*/
const sendInvitationEmail = (user, link) => {
if (!(IsEmail(user.email) && IsUrl(link))) {
return false;
}
// Setup mail transport config
const transporter = nodemailer.createTransport({
service: 'gmail',
host: 'smtp.gmail.com',
port: 587,
auth: {
user: 'bendevtester@gmail.com',
pass: process.env.GMAIL_PASSWORD
}
});

const message = `Hello, <b>${user.name}</b><br/><br/><p>Your friend has invited you to join the ongoing CFH game.<br/><br/>You can click <a href="${link}">here</a> to join the game.</p>`;

// Setup mail transport options
const mailOptions = {
from: '"Bigger Ben" <no-reply@cfh.et>',
to: user.email,
subject: 'CFH Invitation',
html: message
};

// send mail with defined transport object
transporter.sendMail(mailOptions, (error, info) => {
if (error) {
return console.log(error);
}
console.log('Message sent: %s', info.messageId);
});
return true;
};

export default sendInvitationEmail;
2 changes: 0 additions & 2 deletions app/helpers/tokenizer.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import 'babel-polyfill';
import jwt from 'jsonwebtoken';


/**
* @function verifyPayload
* @param {object} data - An object to be encoded into a token
Expand Down
10 changes: 4 additions & 6 deletions app/middleware/auth.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import 'babel-polyfill';
import jwt from 'jsonwebtoken';

/**
* @function auth
* @function verifyToken
* @param {string} data - A token
* @returns { null } returns Unauthorized Access if token is undfefined
* @returns { expired } returns Please login
* @description used to access authenticated route
* @description if token is valid, decode the payload and pass it controller
*/

const auth = (req, res, next) => {
const token = req.headers.authorization || req.headers['x-access-token'];
let token = req.headers.authorization || req.headers['x-access-token'];
token = token.replace('Bearer ', '');

if (!token) {
return res.status(401).json({ message: 'Unauthorized Access' });
}
Expand Down
1 change: 1 addition & 0 deletions app/views/includes/foot.jade
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ script(type='text/javascript', src='/js/controllers/scroll.js')
script(type='text/javascript', src='/js/controllers/header.js')
script(type='text/javascript', src='/js/controllers/game.js')
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')


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 @@ -29,6 +29,7 @@ head
link(rel='stylesheet', href='/css/common.css')
link(rel='stylesheet', href='/css/animate.css')
link(rel='stylesheet', href='/css/style.css')
link(rel='stylesheet', href='/css/find-users.css')

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

This file was deleted.

1 change: 1 addition & 0 deletions backend-test/game/game.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint no-undef: 0 */
import should from 'should';
import io from 'socket.io-client';
import config from '../../config/config';
Expand Down
30 changes: 30 additions & 0 deletions backend-test/helpers/sendEmail.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/* eslint no-undef: 0 */
import { expect } from 'chai';
import sendInvitationEmail from '../../app/helpers/sendInvitationEmail';


describe('Send email invitation', () => {
it('should return true if email goes through', () => {
const user = {
email: 'testemail@gmaill.com',
};
const link = 'http://localhost:4040/app';
expect(sendInvitationEmail(user, link)).to.equal(true);
});

it('should return false if wrong email address is supplied', () => {
const user = {
email: 'wrongEmail.com',
};
const link = 'http://localhost:4040/app';
expect(sendInvitationEmail(user, link)).to.equal(false);
});

it('should return false the link supplied is not a valid URL', () => {
const user = {
email: 'ben@gmail.com',
};
const link = 'htlocalhost:4040/app';
expect(sendInvitationEmail(user, link)).to.equal(false);
});
});
1 change: 1 addition & 0 deletions backend-test/helpers/tokenizer.test.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint no-undef: 0 */
import { expect } from 'chai';
import jwt from 'jsonwebtoken';
import { Tokenizer, decodeToken } from '../../app/helpers/tokenizer';
Expand Down
Loading

0 comments on commit dca3675

Please sign in to comment.