Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add search travel request #33

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"editor.formatOnSave": false,
"eslint.autoFixOnSave": true,
"prettier.eslintIntegration": true,
}
}
71 changes: 56 additions & 15 deletions src/controllers/travelControllers.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import {
onewayTripService,
showManagerPendingAppr,
showUsertravelsStatus
showManagerPendingAppr,
showUsertravelsStatus,
searchTravel
} from '../services/travelServices';
import { successResponseWithData, errorResponse } from '../utils/response';

import message from '../utils/messageUtils';
import statusCode from '../utils/statusCode';
import { paginate } from '../utils/pagination';

export default {
createOneWayTrip: async (req, res) => {
try {
const {
origin, destination, departure_date, accommodation_id, travel_purpose
origin,
destination,
departure_date,
accommodation_id,
travel_purpose
} = req.body;
const userId = req.userData.id;

Expand All @@ -23,12 +28,17 @@ export default {
destination,
departure_date,
travel_purpose,
accommodation_id,
accommodation_id
};

const data = await onewayTripService(travelObj);

successResponseWithData(res, statusCode.created, message.oneWayTripCreated, data);
successResponseWithData(
res,
statusCode.created,
message.oneWayTripCreated,
data
);
} catch (err) {
errorResponse(res, statusCode.serverError, err);
}
Expand All @@ -46,29 +56,60 @@ export default {
try {
const requestsPending = await showManagerPendingAppr(manager);

const filteredRequests = requestsPending.filter(request => request['user.department.line_manager'] !== null);
const filteredRequests = requestsPending.filter(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected newline after '(' function-paren-newline

request => request['user.department.line_manager'] !== null
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unexpected newline before ')' function-paren-newline


const requestNumbers = filteredRequests.length;

// eslint-disable-next-line max-len
successResponseWithData(res, statusCode.success, message.managerApproval(requestNumbers), filteredRequests);
successResponseWithData(
res,
statusCode.success,
message.managerApproval(requestNumbers),
filteredRequests
);
} catch (err) {
errorResponse(res, statusCode.serverError, err);
}
},

getUserTravelStatus: async(req, res) => {
const { role, id } = req.userData;
getUserTravelStatus: async (req, res) => {
const { role, id } = req.userData;

if (role === 'admin') {
return errorResponse(res, statusCode.unauthorized, message.unauthorized);
} else {
if (role === 'admin') {
return errorResponse(res, statusCode.unauthorized, message.unauthorized);
} else {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary 'else' after 'return' no-else-return

try {
const data = await showUsertravelsStatus(id);
return successResponseWithData(res, statusCode.success, message.userApproval, data);
return successResponseWithData(
res,
statusCode.success,
message.userApproval,
data
);
} catch (error) {

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing spaces not allowed no-trailing-spaces

errorResponse(res, statusCode.serverError, error);
}
}
}
}
};

/* search travels
* @param {Object} req - server request
* @param {Object} res - server response
* @param {Object} next - server response
* @returns {Object} - custom response
*/
export const searchTravels = async (req, res) => {
try {
const { body, query } = req;
const { page, perPage } = query;
const { rows, count } = await searchTravel(body, query);
const meta = paginate(page, perPage, count, rows);
return res.status(200).json({ success: { requests: rows, meta } });
} catch (error) {
return res.status(404).json({ error: error });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected property shorthand object-shorthand

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@t4christ Excellent

There is a defined error handler in the utils folder, instead of res.status(404)..... and also a success response too... instead of res.status(200)....

}
};
6 changes: 4 additions & 2 deletions src/routes/travelRoute.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Router } from 'express';

import travelControllers from '../controllers/travelControllers';
import travelValidator from '../validation/travelValidation';
import travelControllers, {searchTravels} from '../controllers/travelControllers';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A space is required after '{' object-curly-spacing
A space is required before '}' object-curly-spacing

import { getToken, verifyToken } from '../middlewares/tokenMiddleware';
import travelValidator,{validateTravelSearch, validateTravelResult} from '../validation/travelValidation';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A space is required after ',' comma-spacing
A space is required after '{' object-curly-spacing
A space is required before '}' object-curly-spacing



const route = Router();
const { createOneWayTrip, pendingManagerApproval, getUserTravelStatus } = travelControllers;
Expand All @@ -13,6 +14,7 @@ route.post('/onewaytrip', getToken, verifyToken, validateTravelRequest, validate

// handles manager pending req approvals route
route.get('/requests/pending/:manager', getToken, verifyToken, pendingManagerApproval);
route.get('/search/travels',validateTravelSearch, validateTravelResult,searchTravels);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A space is required after ',' comma-spacing


route.get('/user/status', getToken, verifyToken, getUserTravelStatus);

Expand Down
62 changes: 50 additions & 12 deletions src/services/travelServices.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
/* eslint-disable no-useless-catch */
import 'core-js/stable';
import 'regenerator-runtime/runtime';
import { Op } from 'sequelize';
import { computeLimitAndOffset } from '../utils/pagination';
import models from '../models';

const { travel_requests, departments, users } = models;

export const onewayTripService = async (travelObj) => {
export const onewayTripService = async travelObj => {
try {
return await travel_requests.create(travelObj);
} catch (err) {
throw err;
}
};

export const findTravelById = async (id) => {
export const findTravelById = async id => {
try {
return await travel_requests.findOne({
attributes: ['id', 'user_id'],
where: { id },
where: { id }
});
} catch (err) {
throw err;
}
};

export const showManagerPendingAppr = async (manager) => {
export const showManagerPendingAppr = async manager => {
try {
return await travel_requests.findAll({
where: {
approval_status: 'pending',
approval_status: 'pending'
},
include: [
{
Expand All @@ -39,20 +41,56 @@ export const showManagerPendingAppr = async (manager) => {
model: departments,
attributes: ['dept_name', 'line_manager'],
where: {
line_manager: manager,
},
},
],
},
line_manager: manager
}
}
]
}
],
raw: true,
raw: true
});
} catch (err) {
throw err;
}
};

export const showUsertravelsStatus = async (userId) => {
/** helper function that get request with a search keyword
* @param {object} body query
* @param {object} query query
* @returns {Promise} Promise resolved or rejected
*/
export const searchTravel = (body, query) => {
const { page, perPage } = query;
const { limit, offset } = computeLimitAndOffset(page, perPage);
const searchValue = Object.keys(body).map(key => {
switch (key) {
case 'origin':
return {
origin: body[key]
};
case 'destination':
return {
destination: body[key]
};

default:
return {
[key]: {
[Op.iLike]: `%${body[key]}`
}
};
}
});
return travel_requests.findAndCountAll({
where: {
[Op.or]: searchValue
},
limit,
offset
});
};

export const showUsertravelsStatus = async userId => {
try {
return await travel_requests.findAll({
where: {
Expand Down
20 changes: 20 additions & 0 deletions src/tests/travel.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,26 @@ describe('Testing one way ticket feature', () => {
done();
});
});


it('should return search result based on origin and destination', (done) => {
const searchTravelRequest = {
'origin': 'NewYork, USA',
'destination': 'Paris, France'
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expected indentation of 4 spaces but found 6 indent
Missing semicolon semi


Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trailing spaces not allowed no-trailing-spaces

chai.request(app)
.get(`${prefix}/search/travels`)
.set('Authorization', `Bearer ${token}`)
.send(searchTravelRequest)
.end((err, res) => {
expect(res.status).to.equal(200);
expect(searchTravelRequest).to.have.property('origin');
expect(searchTravelRequest).to.have.property('destination');
done();
});
});

it('should return an error message if travel_purpose', (done) => {
const mutatedtravelRequest = Object.assign({}, travelRequest);
mutatedtravelRequest.travel_purpose = '';
Expand Down
32 changes: 32 additions & 0 deletions src/utils/pagination.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

/**
* @function computeLimitAndOffset
* @param {number} page page number to get
* @param {number} perPage number of items per page/request
* @param {number} defaultLimit default items per page = 20
* @returns {object} returns object containing limit and offset
*/
export const computeLimitAndOffset = (page, perPage, defaultLimit = 20) => {
const offset = (page ? Number(page) - 1 : 0) * (perPage ? Number(perPage) : defaultLimit);
const limit = perPage ? Number(perPage) : defaultLimit;
return { offset, limit };
};

/**
* @function paginate
* @param {number} page page number to get
* @param {number} perPage number of items per page/request
* @param {number} count total number of items
* @param {array} rows items
* @param {number} defaultLimit default items per page = 20
* @returns {object} return the metaData for pagination
*/
export const paginate = (page, perPage, count, rows, defaultLimit = 20) => {
const metaData = {
page: Number(page) || 1,
pageCount: Math.ceil(count / (perPage ? Number(perPage) : defaultLimit)),
pageSize: rows.length,
count
};
return metaData;
};
45 changes: 38 additions & 7 deletions src/validation/travelValidation.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,64 @@
import { check, validationResult } from 'express-validator';


import { errorResponse } from '../utils/response';
import statusCode from '../utils/statusCode';
import message from '../utils/messageUtils';

export default {
validateTravelRequest: [
check('origin').not().isEmpty()
check('origin')
.not()
.isEmpty()
.withMessage(message.emptyOrigin)
.isAlpha()
.withMessage(message.lettersAlone),
check('destination').not().isEmpty()
check('destination')
.not()
.isEmpty()
.withMessage(message.emptyDestination)
.isAlpha()
.withMessage(message.lettersAlone),
check('departure_date').not().isEmpty().withMessage(message.emptyDepartureDate),
check('travel_purpose').not().isEmpty().withMessage(message.emptyTravelPurpose),
check('departure_date')
.not()
.isEmpty()
.withMessage(message.emptyDepartureDate),
check('travel_purpose')
.not()
.isEmpty()
.withMessage(message.emptyTravelPurpose)
],
validateResult: (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const error = [];
errors.array().forEach((err) => {
errors.array().forEach(err => {
error.push(err.msg);
});
return errorResponse(res, statusCode.badRequest, error);
}
return next();
},
}
};

export const validateTravelSearch = [
check('origin')
.not()
.isInt()
.withMessage(message.lettersAlone),
check('destination')
.not()
.isInt()
.withMessage(message.lettersAlone)
];

export const validateTravelResult = (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const error = [];
errors.array().forEach(err => {
error.push(err.msg);
});
return errorResponse(res, statusCode.badRequest, error);
}
return next();
};