Skip to content

Commit

Permalink
Merge pull request #6 from Mcdavid95/ft-signup-user-167043030
Browse files Browse the repository at this point in the history
#167043030 Signup User
  • Loading branch information
Mcdavid95 authored Jul 8, 2019
2 parents 9209ce8 + ba6ebfa commit 3ae81bb
Show file tree
Hide file tree
Showing 19 changed files with 1,349 additions and 56 deletions.
799 changes: 749 additions & 50 deletions package-lock.json

Large diffs are not rendered by default.

17 changes: 13 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
"description": "WayFarer is a public bus transportation booking server.",
"main": "index.js",
"scripts": {
"build": "babel ./ -d build --only src --ignore node_modules --source-maps inline",
"build": "babel ./ -d build --only src --source-maps inline",
"create-tables": "node build/src/models createTables",
"drop-tables": "node build/src/models dropTables",
"coverage-report": "cat ./coverage/lcov.info | coveralls",
"dev-start": "babel-watch src/index.js",
"lint": "eslint --ignore-path .gitignore .",
"prebuild": "rm -rf build/*",
"prestart": "npm run build",
"pretest": "npm run lint",
"prestart": "npm run build && npm run create-tables",
"pretest": "npm run lint && npm run build && npm run drop-tables && npm run create-tables",
"start": "node build/src/index.js",
"test": "nyc --reporter=text --reporter=html --reporter=lcov mocha --timeout 35000 --require @babel/register ./tests/** --exit && npm run coverage-report"
},
Expand Down Expand Up @@ -50,13 +52,20 @@
"@babel/plugin-transform-runtime": "^7.4.4",
"@babel/preset-env": "^7.4.5",
"@babel/runtime": "^7.4.5",
"@hapi/joi": "^15.1.0",
"babel-watch": "^7.0.0",
"bcrypt": "^3.0.6",
"body-parser": "^1.19.0",
"bunyan": "^1.8.12",
"cors": "^2.8.5",
"debug": "^4.1.1",
"dotenv": "^8.0.0",
"express": "^4.17.1",
"helmet": "^3.18.0",
"jsonwebtoken": "^8.5.1",
"make-runnable": "^1.3.6",
"moment": "^2.24.0",
"morgan": "^1.9.1"
"morgan": "^1.9.1",
"pg": "^7.11.0"
}
}
49 changes: 49 additions & 0 deletions src/controllers/Auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import moment from 'moment';
import db from './db';
import {
createToken,
// hasToken,
hashPassword,
// isPassword,
handleServerError,
handleServerResponse,
handleServerResponseError
} from '../helpers/utils';

const Auth = {
async create(req, res) {
const {
email, firstname, lastname, password, userType
} = req.body;
try {
const hash = await hashPassword(password);
const createQuery = `INSERT INTO
Users(email, first_name, last_name, password, is_admin, created_date, modified_date)
VALUES($1, $2, $3, $4, $5, $6, $7)
returning *`;
const values = [
email.trim().toLowerCase(),
firstname.trim().toLowerCase(),
lastname.trim().toLowerCase(),
hash,
userType === 'admin',
moment(new Date()),
moment(new Date())
];
const { rows } = await db.query(createQuery, values);
const token = createToken(rows[0].id);
return handleServerResponse(res, 201, {
user_id: rows[0].id,
is_admin: rows[0].is_admin,
token
});
} catch (error) {
if (error.routine === '_bt_check_unique') {
return handleServerResponseError(res, 409, `User with Email:- ${email.trim().toLowerCase()} already exists`);
}
handleServerError(res, error);
}
}
};

export default Auth;
Empty file added src/controllers/User.js
Empty file.
26 changes: 26 additions & 0 deletions src/controllers/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Pool } from 'pg';
import dotenv from 'dotenv';

dotenv.config();
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
export default {
/**
* DB Query
* @param {string} text
* @param {Array} params
* @returns {object} object
*/
query(text, params) {
return new Promise((resolve, reject) => {
pool.query(text, params)
.then((res) => {
resolve(res);
})
.catch((err) => {
reject(err);
});
});
}
};
95 changes: 95 additions & 0 deletions src/helpers/utils.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,109 @@
import bunyan from 'bunyan';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';

dotenv.config();

export const logger = () => {
const log = bunyan.createLogger({ name: 'myapp' });
return log;
};

/**
*
* @param {*} response response object from server
* @param {*} status error message
* @param {*} data meta-data
* @returns {*} error response
*/
// eslint-disable-next-line max-len
export const handleServerResponse = (response, status, data) => response.status(status).send({
status: 'success',
data
});

/**
*
* @param {*} response response object from server
* @param {*} status error status
* @param {*} message error message
* @returns {*} error response
*/
// eslint-disable-next-line max-len
export const handleServerResponseError = (response, status, message) => response.status(status).send({
status: 'error',
message
});

export const handleServerError = (res, error) => {
logger().error(error);
return res.status(500).send({
status: 'error',
error: 'Internal Server Error'
});
};

/**
* @function hashPassword
* @param {string} password password to be hashed
* @description hashes a password with bcrypt
* @returns {string} password hash form
*/
export const hashPassword = async (password) => {
const saltRounds = process.env.SALT;
const hash = await bcrypt.hash(password, parseInt(saltRounds, 10));
return hash;
};

/**
* @function isPassword
* @param {string} password in ordinary form
* @param {string} hash password hash form
* @description checks if a password corresponds with saved hash in db
* @returns {boolean} true if correct of false if incorrect
*/
export const isPassword = (password, hash) => bcrypt.compareSync(password, hash);

/**
* createToken
* @param {Number} id user id gotten from DATABASE_URL
* @description creates new jwt token for authentication
* @returns {String} newly created jwt
*/
export const createToken = (id) => {
const token = jwt.sign(
{
id
},
process.env.SECRET, { expiresIn: '7d' }
);
return token;
};

/**
* @method hasToken
* @param {*} req
* @param {*} res
* @param {*} next
* @returns {Object} response object
*/
export const hasToken = (req, res, next) => {
const token = req.body.token || req.headers['x-access-token'];
if (token) {
jwt.verify(token, process.env.SECRET, (err, decoded) => {
if (err) {
return res.status(403).send({
success: false,
message: err
});
}
req.decoded = decoded;
return next();
});
} else {
return res.status(403).send({
message: 'You have to be loggedin first'
});
}
};
33 changes: 33 additions & 0 deletions src/helpers/validateInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Joi from '@hapi/joi';
import { handleServerResponseError } from './utils';

/**
* @function
* @param {*} req
* @param {*} res
* @param {*} next
* @description validates signup input
* @returns {Response | RequestHandler} error or request handler
*/
const signupInput = (req, res, next) => {
const {
firstname, lastname, email, password
} = req.body;
const schema = Joi.object().keys({
firstname: Joi.string().required(),
lastname: Joi.string().required(),
email: Joi.string().trim().email().required(),
password: Joi.string().min(8).required()
});
const result = Joi.validate({
firstname, lastname, email, password
}, schema);
if (result.error) {
return handleServerResponseError(res, 401, result.error.details[0].message);
}
return next();
};

export default {
validateSignup: signupInput
};
8 changes: 7 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import express from 'express';
import { json, urlencoded } from 'body-parser';
import helmet from 'helmet';
import dotenv from 'dotenv';
import cors from 'cors';
import morgan from 'morgan';
import { logger } from './helpers/utils';
import auth from './routes/auth';

dotenv.config();

Expand All @@ -15,13 +17,17 @@ app.use(morgan('dev'));
app.use(helmet())
.disable('x-powered-by')
.use(cors());
app.use(express.json());

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

app.get('/api/v1', (req, res) => res.status(200).send({
status: 'success',
message: 'Welcome Save A Seat API'
}));

app.use('/api/v1/auth', auth);

app.listen(port);
logger().info(`app running on port ${port}`);

Expand Down
64 changes: 64 additions & 0 deletions src/models/booking.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@

import { Pool } from 'pg';
import { logger } from '../helpers/utils';

const dotenv = require('dotenv');

dotenv.config();

const pool = new Pool({
connectionString: process.env.DATABASE_URL
});

pool.on('connect', () => {
logger().info('connected to the db');
});

/**
* Create Tables
* @returns {*} void
*/
export const createBookingTable = async () => {
const client = await pool.connect();
const queryText = `
CREATE TABLE IF NOT EXISTS
Bookings(
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL,
trip_id INTEGER NOT NULL,
created_on TIMESTAMP,
modified_date TIMESTAMP
FOREIGN KEY (user_id) REFERENCES User (id) ON DELETE CASCADE
FOREIGN KEY (trip_id) REFERENCES Trip (id) ON DELETE CASCADE
)`;
try {
const response = await client.query(queryText);
logger().info(response);
} catch (error) {
logger().error(error);
} finally {
client.release();
}
};

/**
* Drop Tables
* @returns {*} void
*/
export const dropBookingTable = async () => {
const client = await pool.connect();
const queryText = 'DROP TABLE IF EXISTS Bookings';
try {
const response = await client.query(queryText);
logger().info(response);
} catch (error) {
logger().error(error);
} finally {
client.release();
}
};

pool.on('remove', () => {
logger().info('client removed');
process.exit(0);
});
Loading

0 comments on commit 3ae81bb

Please sign in to comment.