Skip to content
This repository has been archived by the owner on May 29, 2020. It is now read-only.

Step management set into API #113

Open
wants to merge 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Add csv middleware to list routes when needed
* Add batch crud route to handle csv imports
* Export metrics for prometheus
* Add step management
### Fix
* bump validator to 12.1.0 & deleted node-input-validator
* Updated csv flatten middleware to common csv formatter for rides
Expand Down
20 changes: 20 additions & 0 deletions helpers/date-helpers.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const sortByDate = (array) => array.sort((a, b) => {
const startA = a.date;
const startB = b.date;

if (startA < startB) {
return -1;
}
if (startA > startB) {
return 1;
}
return 0;
});

export const isToday = (date) => {
const today = new Date();

return date.getDate() === today.getDate()
&& date.getMonth() === today.getMonth()
&& date.getFullYear() === today.getFullYear();
};
20 changes: 20 additions & 0 deletions helpers/step-management/generator.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
CREATED,
DELIVERED,
STARTED, VALIDATED,
} from '../../models/status';
import { DEPARTURE, ARRIVAL } from './types';
import Step from './schema';
import { isToday } from '../date-helpers';

export default (ride) => {
const steps = [];
if (ride.status !== DELIVERED && isToday(ride.start)) {
const shouldGenerateDualStep = ride.status === CREATED || ride.status === VALIDATED || ride.status === STARTED;
if (shouldGenerateDualStep) {
steps.push(new Step(DEPARTURE, ride));
}
steps.push(new Step(ARRIVAL, ride));
}
return steps;
};
33 changes: 33 additions & 0 deletions helpers/step-management/merge.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Luxon from 'luxon';

const { DateTime } = Luxon;

export default (steps) => steps.reduce((acc, step) => {
const previous = acc[acc.length - 1];
if (
acc.length < 1
|| (
DateTime.fromJSDate(previous.date).toISO() !== DateTime.fromJSDate(step.date).toISO()
|| previous.destination !== step.destination
)
) {
acc.push(step);
} else {
acc[acc.length - 1] = {
...previous,
rideId: [
...previous.rideId,
step.rideId,
],
phone: [
...previous.phone,
...step.phone,
],
passengersCount: {
key: previous.passengersCount.key,
value: previous.passengersCount.value + step.passengersCount.value,
},
};
}
return acc;
}, []);
50 changes: 50 additions & 0 deletions helpers/step-management/schema.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import nanoid from 'nanoid';
import { ARRIVAL } from './types';
import { VALIDATED, WAITING } from '../../models/status';

export default class Step {
constructor(type, ride) {
this.id = nanoid();
this.rideId = [ride.id];
this.type = type;
this.date = type === ARRIVAL ? ride.end : ride.start;
this.destination = type === ARRIVAL ? ride.arrival.label : ride.departure.label;
this.status = type === ARRIVAL ? WAITING : VALIDATED;
this.phone = ride.phone ? [ride.phone] : [];
this.generatePassengerCount(ride.passengersCount);
this.generateDetails(
ride.comments,
ride.luggage,
);
}

generatePassengerCount(passengersCount) {
let key;
if (this.type === ARRIVAL) {
key = `Dépôt de passager${passengersCount > 1 ? 's' : ''}`;
} else {
key = `Prise en charge de passager${passengersCount > 1 ? 's' : ''}`;
}
this.passengersCount = {
key,
value: passengersCount,
};
}

generateDetails(comments, luggage) {
const details = [];
if (comments) {
details.push({
key: 'Commentaire',
value: comments,
});
}
if (luggage) {
details.push({
key: 'Présence de bagages',
value: 'Oui',
});
}
this.details = details;
}
}
2 changes: 2 additions & 0 deletions helpers/step-management/types.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DEPARTURE = 'departure';
export const ARRIVAL = 'arrival';
14 changes: 14 additions & 0 deletions middlewares/rides-to-steps.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { sortByDate } from '../helpers/date-helpers';
import convertRideToSteps from '../helpers/step-management/generator';
import mergeSimilarSteps from '../helpers/step-management/merge';

export default async (ctx, next) => {
await next();
if (ctx.query.filters && ctx.query.filters.format === 'steps') {
const sortedSteps = sortByDate(ctx.body.reduce((acc, ride) => [
...acc,
...convertRideToSteps(ride),
], []));
ctx.body = mergeSimilarSteps(sortedSteps);
}
};
11 changes: 8 additions & 3 deletions models/status.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export const CREATE = 'create';
export const VALIDATE = 'validation';
export const REJECT_BOUNDARY = 'rejection_boundary';
export const REJECT_CAPACITY = 'rejection_capacity';
export const ACCEPT = 'accept';
export const DECLINE = 'decline';
export const START = 'start-up';
export const WAIT = 'stay';
Expand All @@ -44,6 +43,13 @@ export const CANCEL_TECHNICAL = 'cancel_technical';
export const CANCEL_REQUESTED_CUSTOMER = 'cancel_requested_by_customer';
export const CANCEL_CUSTOMER_OVERLOAD = 'cancel_customer_overload';
export const CANCEL_CUSTOMER_MISSING = 'cancel_customer_missing';
export const CANCEL_STATUSES = [
CANCEL,
CANCEL_TECHNICAL,
CANCEL_REQUESTED_CUSTOMER,
CANCEL_CUSTOMER_OVERLOAD,
CANCEL_CUSTOMER_MISSING,
];

export const CANCELABLE = [CREATED, VALIDATED, ACCEPTED, STARTED, WAITING, IN_PROGRESS];

Expand All @@ -54,9 +60,8 @@ export default {
{ name: VALIDATE, from: CREATED, to: VALIDATED },
{ name: REJECT_BOUNDARY, from: CREATED, to: REJECTED_BOUNDARY },
{ name: REJECT_CAPACITY, from: CREATED, to: REJECTED_CAPACITY },
{ name: ACCEPT, from: VALIDATED, to: ACCEPTED },
{ name: DECLINE, from: VALIDATED, to: DECLINED },
{ name: START, from: ACCEPTED, to: STARTED },
{ name: START, from: VALIDATED, to: STARTED },
{ name: WAIT, from: STARTED, to: WAITING },
{ name: PROGRESS, from: WAITING, to: IN_PROGRESS },
{ name: DELIVER, from: IN_PROGRESS, to: DELIVERED },
Expand Down
3 changes: 2 additions & 1 deletion routes/campuses/drivers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ensureThatFiltersExists } from '../../middlewares/query-helper';
import { emitDriversSocketConnected } from '../../middlewares/drivers-socket-status';
import config from '../../services/config';
import contentNegociation from '../../middlewares/content-negociation';

import convertToStepsMiddleware from '../../middlewares/rides-to-steps';

const router = new Router();
const addDomainInError = (e) => [
Expand Down Expand Up @@ -63,6 +63,7 @@ router.get(
'/:driver_id/rides',
resolveRights(CAN_LIST_CAMPUS_DRIVER_RIDE),
maskOutput,
convertToStepsMiddleware,
ensureThatFiltersExists('status'),
async (ctx) => {
const { filters } = ctx.query;
Expand Down
59 changes: 43 additions & 16 deletions routes/rides.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Luxon from 'luxon';
import {
CANCEL_REQUESTED_CUSTOMER,
CANCELED_STATUSES, CREATE, DELIVERED, DRAFTED,
CANCEL_STATUSES,
} from '../models/status';
import maskOutput, { cleanObject } from '../middlewares/mask-output';
import contentNegociation from '../middlewares/content-negociation';
Expand All @@ -29,6 +30,7 @@ import {
CAN_DELETE_SELF_RIDE,
} from '../models/rights';
import { getPrefetchedRide, prefetchRideMiddleware } from '../helpers/prefetch-ride';
import convertRideToSteps from '../helpers/step-management/generator';

const { DateTime } = Luxon;

Expand All @@ -39,6 +41,14 @@ function ioEmit(ctx, data, eventName = '', rooms = []) {
});
io.emit(eventName, data);
}
const getRooms = (ride) => ({
rooms: [
`ride/${ride.id}`,
`campus/${ride.campus.id}`,
],
driverRoom: [`driver/${ride.driver.id}`],
});

const REQUEST_PRE_MASK = 'start,campus/id,departure/id,arrival/id,luggage,passengersCount,userComments,status';
const REQUEST_POST_MASK = 'id,start,campus/id,departure/id,arrival/id,luggage,passengersCount,userComments,status';
const router = generateCRUD(Ride, {
Expand All @@ -62,11 +72,15 @@ const router = generateCRUD(Ride, {
ctx.body = mask(ctx.body, REQUEST_POST_MASK);
}
ctx.log(ctx.log.INFO, `${Ride.modelName} "${ride.id}" has been created`);
ioEmit(ctx, cleanObject(ctx.body), 'rideUpdate', [
`ride/${ride.id}`,
`campus/${ride.campus.id}`,
`driver/${ride.driver.id}`,
]);

const { rooms, driverRoom } = getRooms(ride);

if (!isRequest) {
const steps = convertRideToSteps(ride);
ioEmit(ctx, cleanObject(steps), 'rideUpdate', driverRoom);
}
ioEmit(ctx, cleanObject(ctx.body), 'rideUpdate', rooms);

// Todo: add this to a queue system to ensure this will be executed
NotificationDevice.findOneByUser(ride.driver.id).then((device) => {
if (device) {
Expand Down Expand Up @@ -118,14 +132,16 @@ const router = generateCRUD(Ride, {
`${Ride.modelName} "${id}" has been modified`,
{ body },
);
const rooms = [
`ride/${ride.id}`,
`campus/${ride.campus.id}`,
`driver/${ride.driver.id}`,
];

const { rooms, driverRoom } = getRooms(ride);
if (previousDriverId && body.driver.id !== previousDriverId) {
rooms.push(`driver/${previousDriverId}`);
driverRoom.push(`driver/${previousDriverId}`);
}

if (ctx.query && ctx.query.step) {
const { step } = ctx.query;
step.status = ctx.body.status;
ioEmit(ctx, [step], 'rideUpdate', driverRoom);
}

ioEmit(ctx, cleanObject(ctx.body), 'rideUpdate', rooms);
Expand Down Expand Up @@ -268,6 +284,7 @@ router.post(
async (ctx) => {
// @todo: rights - driver should be able to update only some status
const { params: { id, action } } = ctx;

if (!ctx.may(CAN_EDIT_RIDE_STATUS) && action !== CREATE && action !== CANCEL_REQUESTED_CUSTOMER) {
ctx.throw_and_log(403, `You're not authorized to mutate to "${action}"`);
}
Expand All @@ -286,11 +303,21 @@ router.post(
`${Ride.modelName} "${id}" has been modified`,
{ ride },
);
ioEmit(ctx, cleanObject(ctx.body), 'rideUpdate', [
`ride/${ride.id}`,
`campus/${ride.campus.id}`,
`driver/${ride.driver.id}`,
]);

const { rooms, driverRoom } = getRooms(ride);

const isActionCancel = CANCEL_STATUSES.filter((status) => action === status).length > 0;
if (isActionCancel) {
ioEmit(ctx, ride._id, 'deleteStep', driverRoom);
}

if (ctx.query && ctx.query.step) {
const { step } = ctx.query;
step.status = ctx.body.status;
ioEmit(ctx, cleanObject([step]), 'rideUpdate', driverRoom);
}

ioEmit(ctx, cleanObject(ctx.body), 'rideUpdate', rooms);
},
);

Expand Down