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

#167891600 Update Travel Request #47

Merged
merged 1 commit into from
Sep 10, 2019
Merged
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@
"sequelize-cli": "^5.5.0",
"sequelize-replace-enum-postgres": "1.5.0",
"swagger-ui-express": "^4.0.7",
"underscore": "^1.9.1"
"underscore": "^1.9.1",
"uuid-validate": "0.0.3"
},
"devDependencies": {
"@babel/cli": "^7.5.5",
Expand Down
83 changes: 83 additions & 0 deletions src/controllers/Trip.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,89 @@ class TripController {
);
return res.status(response.code).json(response);
}


/**
*
* @param {object} req Request
* @param {object} res Response
* @returns {object} JSON
*/
static async editTripRequest(req, res) {
try {
const { tripId } = req.params;
const { payload: { id: userId } } = req.payload;
const {
from, departureDate, returnDate, destinations, reason
} = req.body;
// Check if trip exists
const trip = await Trip.findOne(
{
where: { id: tripId, userId },
raw: true
}
);
if (!trip) {
const response = new Response(false, 404, 'Trip does not exist');
return res.status(response.code).json(response);
}
// check if trip is still open
if (trip.status !== 'pending') {
const response = new Response(false, 403, 'This trip is no longer open');
return res.status(response.code).json(response);
}
// update record in Trip table
await Trip.update(
{
startBranchId: from,
tripDate: departureDate,
returnDate: trip.type === 'return' ? returnDate : null,
reason,
},
{ where: { id: tripId } }
);

// update records in Stops table
if (destinations) {
const modifiedDestinations = destinations.map((destination) => Stop.update(
{
destinationBranchId: destination.to,
accomodationId: destination.accomodation,
},
{ where: { id: destination.id }, returning: true, plain: true }
));
await Promise.all(modifiedDestinations);
}
// Fetch trip to populate response body
const updatedTrip = await Trip.findOne(
{
where: { id: tripId },
include: [
{
model: Stop,
as: 'stop',
attributes: { exclude: ['tripId'] }
},
],
order: [[{ model: Stop, as: 'stop' }, 'createdAt', 'ASC']],
}
);
const response = new Response(
true,
200,
'Travel request successfully updated',
{ trip: updatedTrip }
);
return res.status(response.code).json(response);
} catch (error) {
const response = new Response(
false,
500,
'Server error, Please try again later'
);
return res.status(response.code).json(response);
}
}
}

export default TripController;
7 changes: 6 additions & 1 deletion src/database/seeders/20190829125545-create-branches.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ module.exports = {
id: '3dd3b34a-7553-455e-a688-36afda199614',
name: 'Calabar South',
locationId: '0190ae78-d184-4258-add5-0b2c6982efef'
},
{
id: '1bf9f868-2a59-45ae-8f68-ad68095ca0ac',
name: 'Asokoro',
locationId: '0190ae78-d184-4258-add5-0b2c6982efef'
}
]),
down: queryInterface => queryInterface.bulkDelete('Branches', null, {})
down: (queryInterface) => queryInterface.bulkDelete('Branches', null, {})
};
39 changes: 38 additions & 1 deletion src/database/seeders/20190829125546-create-trips.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,44 @@ module.exports = {
returnDate: null,
reason: 'to meet with clients',
status: 'approved'
}
},
{
id: '72a5c8f5-27eb-4623-b6f4-09f77cc871f3',
type: 'oneway',
startBranchId: '3dd3b34a-7554-425e-a688-36afda199614',
userId: '91542e6f-94bc-4e80-a667-586fb0752f24',
tripDate: '2019-02-17',
reason: 'for holiday',
status: 'pending'
},
{
id: 'ba3f6b93-f09e-49a4-bb28-c65555250bc1',
type: 'return',
startBranchId: '3dd3b34a-7554-425e-a688-36afda199614',
userId: '91542e6f-94bc-4e80-a667-586fb0752f24',
tripDate: '2019-02-17',
returnDate: '2019-02-23',
reason: 'for holiday',
status: 'pending'
},
{
id: 'db17ddb2-a6ba-49f5-9715-e0e81eb7720a',
type: 'multiple',
startBranchId: '3dd3b34a-7554-425e-a688-36afda199614',
userId: '91542e6f-94bc-4e80-a667-586fb0752f24',
tripDate: '2019-02-17',
reason: 'for holiday',
status: 'pending'
},
{
id: '4ae4fef9-8e5e-4d2a-879a-a0425cd3d5aa',
type: 'oneway',
startBranchId: '3dd3b34a-7554-425e-a688-36afda199614',
userId: '91542e6f-94bc-4e80-a667-586fb0752f24',
tripDate: '2019-02-17',
reason: 'for holiday',
status: 'approved'
},
]),
down: queryInterface => queryInterface.bulkDelete('Trips', null, {})
};
24 changes: 24 additions & 0 deletions src/database/seeders/20190829130101-create-stops.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,30 @@ module.exports = {
destinationBranchId: '3dd3b34a-7554-455e-a688-36afda199624',
accomodationId: '3dd3b34a-7554-425e-c688-36afda199619',
tripId: 'ffe25dbe-49ea-4759-8462-ed116f6749df'
},
{
id: 'd3d9c312-6ac0-4f9a-a371-efa587e8f56c',
destinationBranchId: '3dd3b34a-7554-455e-a688-36afda199624',
accomodationId: '3dd3b34a-7554-425e-c688-36afda199619',
tripId: '72a5c8f5-27eb-4623-b6f4-09f77cc871f3'
},
{
id: 'a041b2c9-eaba-4d8d-87b1-378d557b2678',
destinationBranchId: '3dd3b34a-7554-455e-a688-36afda199624',
accomodationId: '3dd3b34a-7554-425e-c688-36afda199619',
tripId: 'ba3f6b93-f09e-49a4-bb28-c65555250bc1'
},
{
id: '13020a26-5840-4758-93bd-9da91920d1e8',
destinationBranchId: '3dd3b34a-7554-455e-a688-36afda199624',
accomodationId: '3dd3b34a-7554-425e-c688-36afda199619',
tripId: 'db17ddb2-a6ba-49f5-9715-e0e81eb7720a'
},
{
id: '79d09466-ee4f-4e96-a38b-009e4595121e',
destinationBranchId: '1bf9f868-2a59-45ae-8f68-ad68095ca0ac',
accomodationId: '3dd3b34a-7554-425e-c688-36afda199619',
tripId: 'db17ddb2-a6ba-49f5-9715-e0e81eb7720a'
}
]),
down: queryInterface => queryInterface.bulkDelete('Stops', null, {})
Expand Down
11 changes: 10 additions & 1 deletion src/routes/trip.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import Token from '../helpers/Token';
import validator from '../middlewares/validator';
import permit from '../middlewares/permission';
import verify from '../middlewares/AuthMiddlewares';
import { tripRequestSchema, tripRequestStatusSchema } from '../validation/tripSchema';
import {
tripRequestSchema, tripRequestStatusSchema, editTripRequestSchema
} from '../validation/tripSchema';

const tripRoutes = Router();

Expand All @@ -17,6 +19,13 @@ tripRoutes.post(
validator(tripRequestSchema),
TripController.createTripRequest
);
tripRoutes.put(
'/:tripId',
Token.verifyToken,
verify.isUserVerified,
validator(editTripRequestSchema),
TripController.editTripRequest
);

tripRoutes.patch(
'/:tripId',
Expand Down
120 changes: 113 additions & 7 deletions src/validation/tripSchema.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { body, check } from 'express-validator';
import { isValid, parseISO } from 'date-fns';
import { body, check, param } from 'express-validator';
import {
isValid, parseISO, isFuture
} from 'date-fns';
import validateUUID from 'uuid-validate';
import models from '../database/models';

const { Branch, Accomodation } = models;
const { Branch, Accomodation, Stop } = models;

const tripRequestSchema = [
body('type')
Expand All @@ -27,15 +30,15 @@ const tripRequestSchema = [
body('departureDate')
.not().isEmpty({ ignore_whitespace: true })
.withMessage('Departure date is required')
.custom((value) => isValid(parseISO(value)))
.custom((value) => isValid(parseISO(value)) && isFuture(new Date(value)))
.withMessage('Invalid departure date format')
.customSanitizer((value) => new Date(value)),
body('returnDate')
.trim()
.custom((value, { req }) => {
const { type, departureDate } = req.body;
if (type === 'return') {
if (!isValid(parseISO(value))) {
if (!isValid(parseISO(value)) || !isFuture(new Date(value))) {
throw new Error('Invalid return date format');
}
if (new Date(value) <= new Date(departureDate)) {
Expand Down Expand Up @@ -100,7 +103,7 @@ const tripRequestStatusSchema = [
.trim()
.custom((value) => ['pending', 'approved', 'rejected'].includes(value))
.withMessage('Invalid trip status'),

check('tripId')
.exists()
.withMessage('Trip Id is required')
Expand All @@ -109,4 +112,107 @@ const tripRequestStatusSchema = [
.withMessage('Invalid trip id')
];

export { tripRequestStatusSchema, tripRequestSchema };
const editTripRequestSchema = [
param('tripId')
.isUUID(4)
.withMessage('Invalid trip Id'),
body('from')
.optional()
.not().isEmpty({ ignore_whitespace: true })
.withMessage('Starting point is required')
.trim()
.isUUID(4)
.withMessage('Invalid starting point')
.custom(async (value) => {
const branch = await Branch.findOne({ where: { id: value } });
if (!branch) {
throw new Error('Start branch location does not exist');
}
return true;
}),
body('departureDate')
.optional()
.not().isEmpty({ ignore_whitespace: true })
.withMessage('Departure date is required')
.custom((value) => isValid(parseISO(value)) && isFuture(new Date(value)))
.withMessage('Invalid departure date format')
.customSanitizer((value) => new Date(value)),
body('returnDate')
.optional()
.trim()
.custom((value, { req }) => {
const { departureDate } = req.body;
if (!isValid(parseISO(value)) || !isFuture(new Date(value))) {
throw new Error('Invalid return date format');
}
if (new Date(value) <= new Date(departureDate)) {
throw new Error('Return date must be greater than departure date');
}
return true;
})
.customSanitizer((value) => new Date(value)),
body('reason')
.optional()
.not().isEmpty({ ignore_whitespace: true })
.withMessage('Travel reason is required')
.trim(),
body('destinations')
.optional()
.exists()
.withMessage('Destination(s) is required')
.isArray()
.withMessage('Invalid destination(s) format'),
body('destinations.*.id')
.custom(async (value, { req }) => {
if (req.body.destinations) {
if (!validateUUID(value, 4)) {
throw new Error('Invalid destination Id format');
}
const stop = await Stop.findOne({ where: { id: value } });
if (!stop) {
throw new Error('Destination does not exist');
}
}
return true;
}),
body('destinations.*.to')
.optional()
.exists()
.withMessage('Destination is required')
.trim()
.isUUID(4)
.withMessage('Invalid destination format')
.custom(async (value, { req }) => {
const { destinations } = req.body;
const branch = await Branch.findOne({ where: { id: value } });
if (!branch) {
throw new Error('Destination branch does not exist');
}
if (value === req.body.from) {
throw new Error('Start and Destination branch should not be the same');
}
const filtered = destinations.filter(
(destination) => destination.to === value
);
if (filtered.length > 1) {
throw new Error('One or more destination branches are the same');
}
return true;
}),
body('destinations.*.accomodation')
.optional()
.exists()
.withMessage('Accomodation is required')
.trim()
.isUUID(4)
.withMessage('Invalid accomodation format')
.custom(async (value) => {
const accomodation = await Accomodation.findOne({ where: { id: value } });
if (!accomodation) {
throw new Error('Accomodation does not exist');
}
return true;
}),
];

export { tripRequestStatusSchema, tripRequestSchema, editTripRequestSchema };
Loading