Skip to content

Commit

Permalink
Merge pull request #43 from akhilome/ft-user-signup-160819724
Browse files Browse the repository at this point in the history
#160819724 Customer can sign up for an account
  • Loading branch information
akhilome committed Oct 1, 2018
2 parents db02d22 + 514445b commit 6624a4a
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 6 deletions.
10 changes: 8 additions & 2 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"presets": [
"env",
"stage-2"
"stage-2",
[
"env", {
"targets": {
"node": "8.12.0"
}
}
]
]
}
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ node_js:
cache:
directories:
- "node_modules"
services:
- postgresql
before_script:
- psql -c 'create database fastfoodfast;' -U postgres
after_success:
- npm run cover
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.7.0",
"babel-preset-stage-2": "^6.24.1",
"bcryptjs": "^2.4.3",
"body-parser": "^1.18.3",
"dotenv": "^6.0.0",
"express": "^4.16.3",
Expand Down
33 changes: 33 additions & 0 deletions server/controllers/authController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import bcrpyt from 'bcryptjs';
import pool from '../db/config';

class AuthController {
static async signup(req, res) {
const { name, email, password } = req;
const isAdmin = email === 'hovkard@gmail.com' ? 't' : 'f';

try {
// Check if a user with the provided email already exists
const existingUser = (await pool.query('SELECT * FROM users WHERE email=$1', [email])).rowCount;
if (existingUser) {
return res.status(400).json({
status: 'error',
message: 'a user with that email already exists',
});
}
// Hash password and save user to database
const hashedPassword = await bcrpyt.hash(password, 10);
const dbQuery = 'INSERT INTO users(name, email, password, is_admin) VALUES($1, $2, $3, $4) RETURNING id, name, email';
const user = (await pool.query(dbQuery, [name, email, hashedPassword, isAdmin])).rows[0];
return res.status(201).json({
status: 'success',
message: 'user created successfully',
user,
});
} catch (error) {
return res.status(400).json({ error });
}
}
}

export default AuthController;
6 changes: 3 additions & 3 deletions server/db/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { Pool } from 'pg';
dotenv.config();

const env = process.env.NODE_ENV || 'development';
/* eslint-disable */
let pool;
let testPool;

if (env === 'test') {
testPool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
pool = new Pool({ connectionString: process.env.TEST_DATABASE_URL });
} else {
pool = new Pool({ connectionString: process.env.DATABASE_URL });
}

export default { pool, testPool };
export default pool;
5 changes: 4 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import express from 'express';
import bodyParser from 'body-parser';
import dotenv from 'dotenv';
import router from './routes/routes';
import authRouter from './routes/authRouter';

dotenv.config();
const app = express();
Expand All @@ -13,9 +14,11 @@ app.get('/', (req, res) => {
});

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.urlencoded({ extended: false }));

app.use('/api/v1', router);
// Auth routes
app.use('/api/v1/auth/', authRouter);

app.listen(process.env.PORT);

Expand Down
43 changes: 43 additions & 0 deletions server/middleware/sanitizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Validator from '../validators/validator';

class Sanitize {
static signup(req, res, next) {
const {
name,
email,
password,
confirmPassword,
} = req.body;

const missingFields = [name, email, password, confirmPassword].map((field, index) => {
const keys = {
0: 'name',
1: 'email',
2: 'password',
3: 'confirm password',
};
return field === undefined ? keys[index] : null;
}).filter(field => field !== null).join(', ');

if (!name || !email || !password || !confirmPassword) {
return res.status(400).json({
status: 'error',
message: `you're missing these fields: ${missingFields}`,
});
}

const response = message => res.status(400).json({ status: 'error', message });

if (!Validator.isValidName(name)) return response('invalid name');
if (!Validator.isEmail(email)) return response('invalid email');
if (!Validator.isMatchingPasswords(password, confirmPassword)) return response('provided passwords donot match');
if (!Validator.isValidPassword(password)) return response('invalid password');

req.name = name.trim();
req.email = email.trim();
req.password = password.trim();
return next();
}
}

export default Sanitize;
8 changes: 8 additions & 0 deletions server/routes/authRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Router } from 'express';
import AuthController from '../controllers/authController';
import Sanitize from '../middleware/sanitizer';

const router = new Router();
router.post('/signup', Sanitize.signup, AuthController.signup);

export default router;
24 changes: 24 additions & 0 deletions server/validators/validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Validator {
static isEmail(email) {
const re = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/ig;
return re.test(email.trim().toLowerCase());
}

static isValidPassword(password) {
return password.trim().length > 5;
}

static isMatchingPasswords(password, confirmPassword) {
return password.trim() === confirmPassword.trim();
}

static isValidName(name) {
return name.trim().length >= 2;
}
}

/* Refs.
email regex pattern credit: https://www.regular-expressions.info/email.html?wlr=1
*/

export default Validator;
100 changes: 100 additions & 0 deletions tests/routes/auth.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import chai from 'chai';
import 'chai/register-should';
import chaiHttp from 'chai-http';
import dirtyChai from 'dirty-chai';
import app from '../../server/index';
import { seedData, populateTables } from '../seed/seed';

chai.use(chaiHttp);
chai.use(dirtyChai);

describe('POST /auth/signup', () => {
before(populateTables);

it('should signup a valid user successfully', (done) => {
chai.request(app)
.post('/api/v1/auth/signup')
.send(seedData.users.admin)
.end((err, res) => {
if (err) done(err);

res.status.should.eql(201);
res.body.should.be.an('object').that.has.keys(['status', 'message', 'user']);
res.body.status.should.eql('success');
res.body.user.should.have.keys(['id', 'name', 'email']);
res.body.user.name.should.eql(seedData.users.admin.name);
res.body.user.email.should.eql(seedData.users.admin.email);
done();
});
});

it('should not signup a user with no name', (done) => {
chai.request(app)
.post('/api/v1/auth/signup')
.send(seedData.users.invalidUserNoName)
.end((err, res) => {
if (err) done(err);

res.status.should.eql(400);
res.body.should.have.keys(['status', 'message']);
res.body.should.not.have.keys(['user']);
done();
});
});

it('should not signup a user with no email', (done) => {
chai.request(app)
.post('/api/v1/auth/signup')
.send(seedData.users.invalidUserNoEmail)
.end((err, res) => {
if (err) done(err);

res.status.should.eql(400);
res.body.should.have.keys(['status', 'message']);
res.body.should.not.have.keys(['user']);
done();
});
});

it('should not signup a user with no password', (done) => {
chai.request(app)
.post('/api/v1/auth/signup')
.send(seedData.users.invalidUserNoPass)
.end((err, res) => {
if (err) done(err);

res.status.should.eql(400);
res.body.should.have.keys(['status', 'message']);
res.body.should.not.have.keys(['user']);
done();
});
});

it('should not signup a user with missmatching passwords', (done) => {
chai.request(app)
.post('/api/v1/auth/signup')
.send(seedData.users.invalidUserPassMissMatch)
.end((err, res) => {
if (err) done(err);
res.status.should.eql(400);

res.body.should.have.keys(['status', 'message']);
res.body.should.not.have.keys(['user']);
res.body.message.should.eql('provided passwords donot match');
done();
});
});

it('should not signup a user if email already exists', (done) => {
chai.request(app)
.post('/api/v1/auth/signup')
.send(seedData.users.admin)
.end((err, res) => {
if (err) done(err);
res.status.should.eql(400);
res.body.status.should.eql('error');
res.body.message.should.eql('a user with that email already exists');
done();
});
});
});
56 changes: 56 additions & 0 deletions tests/seed/seed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import pool from '../../server/db/config';

const seedData = {
users: {
admin: {
name: 'Kizito',
email: 'hovkard@gmail.com',
password: 'suppersecurepassword',
confirmPassword: 'suppersecurepassword',
},
validUser: {
name: 'James',
email: 'daniel@james.com',
password: 'pixel2user',
confirmPassword: 'pixel2user',
},
invalidUserNoData: {},
invalidUserNoName: {
email: 'unserious@lad.com',
password: 'insecure',
confirmPassword: 'insecure',
},
invalidUserNoEmail: {
name: 'Name?',
password: 'pass',
confirmPassword: 'pass',
},
invalidUserNoPass: {
name: 'Magician',
email: 'an@email.address',
},
invalidUserPassMissMatch: {
name: 'Olodo',
email: 'another@sweet.email',
password: 'oneThing',
confirmPassword: 'anEntirelyDifferentThing',
},
},
};

const populateTables = async () => {
const dropUsersTableQuery = 'DROP TABLE IF EXISTS users';

const createUsersTableQuery = `CREATE TABLE users (
id serial PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
is_admin BOOLEAN NOT NULL
)`;

await pool.query(dropUsersTableQuery);
await pool.query(createUsersTableQuery);
};

export { seedData, populateTables };

0 comments on commit 6624a4a

Please sign in to comment.