Skip to content

Commit

Permalink
feature(bus): add create bus feature
Browse files Browse the repository at this point in the history
- add endpoint to create bus

- write test to check for edge cases

[Finishes #167195764]
  • Loading branch information
Mcdavid95 committed Jul 10, 2019
1 parent 32cadcd commit 53dc8fb
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 5 deletions.
4 changes: 2 additions & 2 deletions src/controllers/Auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const Auth = {
moment(new Date())
];
const { rows } = await db.query(createQuery, values);
const token = createToken(rows[0].id);
const token = createToken(rows[0].id, rows[0].is_admin);
return handleServerResponse(res, 201, {
user_id: rows[0].id,
is_admin: rows[0].is_admin,
Expand Down Expand Up @@ -62,7 +62,7 @@ const Auth = {
if (!isPassword(password, rows[0].password)) {
return handleServerResponseError(res, 403, 'Password incorrect');
}
const token = createToken(rows[0].id);
const token = createToken(rows[0].id, rows[0].is_admin);
return handleServerResponse(res, 200, { user_id: rows[0].id, token });
} catch (error) {
return handleServerError(res, error);
Expand Down
40 changes: 40 additions & 0 deletions src/controllers/Bus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import moment from 'moment';
import db from './db';
import {
handleServerError,
handleServerResponse,
handleServerResponseError,
} from '../helpers/utils';

export default {
async create(req, res) {
const {
// eslint-disable-next-line camelcase
model, numberPlate, manufacturer, year, capacity
} = req.body;
try {
const createQuery = `INSERT INTO
Buses(model, number_plate, manufacturer, year, capacity, created_date, modified_date)
VALUES($1, $2, $3, $4, $5, $6, $7)
returning *`;
const values = [
model.trim().toLowerCase(),
numberPlate.trim().toLowerCase(),
manufacturer.trim().toLowerCase(),
year,
capacity,
moment(new Date()),
moment(new Date())
];
const { rows } = await db.query(createQuery, values);
return handleServerResponse(res, 201, {
bus: rows[0]
});
} catch (error) {
if (error.routine === '_bt_check_unique') {
return handleServerResponseError(res, 409, `Bus with number plate:- ${numberPlate.trim().toLowerCase()} already exists`);
}
handleServerError(res, error);
}
},
};
25 changes: 23 additions & 2 deletions src/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,14 @@ export const isPassword = (password, hash) => bcrypt.compareSync(password, hash)
/**
* createToken
* @param {Number} id user id gotten from DATABASE_URL
* @param {Number} isAdmin value of if user is an admin
* @description creates new jwt token for authentication
* @returns {String} newly created jwt
*/
export const createToken = (id) => {
export const createToken = (id, isAdmin) => {
const token = jwt.sign(
{
id
id, isAdmin
},
process.env.SECRET, { expiresIn: '7d' }
);
Expand Down Expand Up @@ -109,3 +110,23 @@ export const hasToken = async (req, res, next) => {
return handleServerResponseError(res, 403, error);
}
};

/**
* @method hasToken
* @param {*} req
* @param {*} res
* @param {*} next
* @returns {Object} response object
*/
export const isAdmin = async (req, res, next) => {
const token = req.body.token || req.headers['x-access-token'];
try {
const decoded = await jwt.verify(token, process.env.SECRET);
if (!decoded.isAdmin) {
return handleServerResponseError(res, 403, 'You are not authorized to access this endpoint');
}
return next();
} catch (error) {
return handleServerResponseError(res, 403, error);
}
};
32 changes: 31 additions & 1 deletion src/helpers/validateInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,38 @@ const signinInput = (req, res, next) => {
return next();
};

/**
* @function
* @param {*} req
* @param {*} res
* @param {*} next
* @description validates create bus input
* @returns {Response | RequestHandler} error or request handler
*/
const createBusInput = (req, res, next) => {
const {
// eslint-disable-next-line camelcase
model, manufacturer, year, numberPlate, capacity
} = req.body;
const schema = Joi.object().keys({
model: Joi.string().required(),
manufacturer: Joi.string().required(),
year: Joi.string().trim().required(),
numberPlate: Joi.string().required(),
capacity: Joi.number().required()
});
const result = Joi.validate({
model, manufacturer, year, numberPlate, capacity
}, schema);
if (result.error) {
return handleServerResponseError(res, 401, result.error.details[0].message);
}
return next();
};


export default {
validateSignup: signupInput,
validateSignin: signinInput
validateSignin: signinInput,
validateCreateBus: createBusInput
};
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import cors from 'cors';
import morgan from 'morgan';
import { logger } from './helpers/utils';
import auth from './routes/auth';
import bus from './routes/bus';

dotenv.config();

Expand All @@ -27,6 +28,7 @@ app.get('/api/v1', (req, res) => res.status(200).send({
}));

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

app.listen(port);
logger().info(`app running on port ${port}`);
Expand Down
13 changes: 13 additions & 0 deletions src/routes/bus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import express from 'express';
import Bus from '../controllers/Bus';
import ValidateInput from '../helpers/validateInput';
import { isAdmin, hasToken } from '../helpers/utils';

const { create } = Bus;
const { validateCreateBus } = ValidateInput;

const router = express.Router();

router.post('/', hasToken, isAdmin, validateCreateBus, create);

export default router;
14 changes: 14 additions & 0 deletions tests/__mocks__/bus.mocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const bus = {
model: 'Hiace',
manufacturer: 'Toyota',
year: '2012',
numberPlate: 'fh3du5',
capacity: 16
};

export const incompleteBus = {
model: 'Hiace',
manufacturer: 'Toyota',
year: '2012',
capacity: 16
};
65 changes: 65 additions & 0 deletions tests/controllers/bus.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import chai from 'chai';
import api from '../test.config';
import {
normalUser, adminUser
} from '../__mocks__/auth.mocks';
import { bus } from '../__mocks__/bus.mocks';

const { expect } = chai;
let adminToken,
userToken;

describe('Bus controller', () => {
it('should login an admin', async () => {
const server = await api.post('/api/v1/auth/signin')
.type('form')
.set('Content-Type', 'application/json')
.send(adminUser);
adminToken = server.body.data.token;
expect(server.statusCode).to.equal(200);
});

it('should create a new bus', async () => {
const server = await api.post('/api/v1/bus')
.type('form')
.set('Content-Type', 'application/json')
.set('x-access-token', adminToken)
.send(bus);
expect(server.statusCode).to.equal(201);
});

it('should not create a new bus if token is not present', async () => {
const server = await api.post('/api/v1/bus')
.type('form')
.set('Content-Type', 'application/json')
.send(bus);
expect(server.statusCode).to.equal(403);
});

it('should signin a user', async () => {
const server = await api.post('/api/v1/auth/signin')
.type('form')
.set('Content-Type', 'application/json')
.send(normalUser);
userToken = server.body.data.token;
expect(server.statusCode).to.equal(200);
});

it('should not create a new bus if user is not an admin', async () => {
const server = await api.post('/api/v1/bus')
.type('form')
.set('Content-Type', 'application/json')
.set('x-access-token', userToken)
.send(bus);
expect(server.statusCode).to.equal(403);
});

it('should not create a new bus if plate number exists', async () => {
const server = await api.post('/api/v1/bus')
.type('form')
.set('Content-Type', 'application/json')
.set('x-access-token', adminToken)
.send(bus);
expect(server.statusCode).to.equal(409);
});
});

0 comments on commit 53dc8fb

Please sign in to comment.