delivery_system/src/handlers.ts

208 lines
6.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Request, Response } from 'express';
import dateModule from 'date-and-time';
import { resolveAddress } from './geocoding';
import { Address, AvailableTimeslots, Orders } from './types';
import { randomUUID } from 'crypto';
import { getAvailableTimeSlots } from './services/timeslotsService';
import { getHolidays } from './services/holidaysService';
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
// create a hashing for caching delivery slots.
const deliveriesCache = new Map();
// create a hashing for caching timeslots.
const slotsInUse = new Map();
// orders by dates caching.
const ordersByDates = new Map();
export const resolveAddressHandler = (req: Request, res: Response) => {
if (!req.body.searchTerm) {
res.status(400).json({ error: 'Missing searchTerm' });
return;
}
const address: Promise<Address> = resolveAddress(req.body.searchTerm);
address.then((result) => {
res.status(200).json(result);
return;
})
};
export const timeslotsHandler = async (req: Request, res: Response): Promise<void> => {
if (!req.body.address) {
res.status(400).json({ error: 'Missing address' });
return;
}
const address: Address = req.body.address;
const timeSlots: AvailableTimeslots[] = await availableTimeSlots(address);
const availableTimeSlotsResult: AvailableTimeslots[] = await filterOutHolidaysByCountryCode(address, timeSlots);
availableTimeSlotsResult.forEach((timeSlot, index) => {
if (slotsInUse.has(timeSlot.id) && slotsInUse.get(timeSlot.id).length >= 2) {
availableTimeSlotsResult.splice(index, 1);
}
});
res.status(200).json(availableTimeSlotsResult);
return;
};
export const deliveriesHandler = (req: Request, res: Response): void => {
const userId = req.body.userId;
const slotId = req.body.timeslotId;
// Idea: validate userId and slotId needed
if (!userId || !slotId) {
res.status(400).json({ error: 'Missing userId or slotId' });
return;
}
// Idea: check if user has already booked a delivery - is this needed?
// if (deliveriesCache.has(userId) && deliveriesCache.get(userId).slotId === slotId) {
// res.status(400).json({ error: 'User has already booked a delivery' });
// return;
// }
// Idea: check if timeslot is already full in cache
if (slotsInUse.has(slotId) && slotsInUse.get(slotId).length >= 2) {
res.status(400).json({ error: 'This timeslot is already full' });
return;
}
const deliveryId = randomUUID();
// Idea: create new delivery for user
// the date needs to be in "2023-09-18 14:00:00" format
const deliveryCreatedDate = dateModule.format(new Date(), dateFormat);
const date = deliveryCreatedDate.split(' ')[0];
const delivery = {
_id: deliveryId,
userId,
slotId,
deliveryCreatedDate: deliveryCreatedDate
};
// INFO: This is a very simple implementation of caching.
// INFO: In my opinion, transaction should be used to avoid data inconsistency.
if (slotsInUse.has(slotId)) {
slotsInUse.get(slotId).push(deliveryId);
} else {
slotsInUse.set(slotId, [deliveryId]);
}
if (ordersByDates.has(date)) {
ordersByDates.get(date).push(deliveryId);
} else {
ordersByDates.set(date, [deliveryId]);
}
deliveriesCache.set(deliveryId, delivery);
console.log("ordersByDates", ordersByDates)
console.log("deliveriesCache", deliveriesCache)
console.log("slotsInUse", slotsInUse)
res.status(200).json(delivery);
return;
};
export const cancelDeliveryHandler = (req: Request, res: Response): void => {
// TODO: Implement cancel delivery functionality
// DELETE /deliveries/:deliveryId
const deliveryId = req.params.deliveryId;
if (!deliveryId) {
res.status(400).json({ error: 'Missing deliveryId' });
return;
}
// remove from deliveriesCache and extract slotId
const delivery = deliveriesCache.get(deliveryId);
if (!delivery) {
res.status(400).json({ error: 'Delivery not found' });
return;
}
const slotId = delivery.slotId;
deliveriesCache.delete(deliveryId);
// remove from slotsInUse by slotId
const slot = slotsInUse.get(slotId);
const index = slot.indexOf(deliveryId);
if (index > -1) {
slot.splice(index, 1);
}
res.status(200).json(delivery);
return;
};
export const dailyDeliveriesHandler = (req: Request, res: Response): void => {
// TODO: Implement daily deliveries functionality
// GET /deliveries/daily - retrieve all todays deliveries - by today in ordersByDates
const date = dateModule.format(new Date(), dateFormat).split(' ')[0];
const todaysOrders: Orders[] = [];
if (ordersByDates.has(date)) {
ordersByDates.get(date).forEach((orderId) => {
todaysOrders.push(deliveriesCache.get(orderId));
});
}
res.status(200).json(todaysOrders);
return;
};
export const weeklyDeliveriesHandler = (req: Request, res: Response): void => {
// TODO: Implement weekly deliveries functionality
// GET /deliveries/weekly - retrieve all week deliveries - from today to 7 days later in ordersByDates
// this array will contain 7 elements - each element will be dates in the next 7 days
// ["today", "tomorrow" ... ]
const today = new Date();
const weeklyOrders: Orders [] = [];
for (let i = 0; i < 7; i++) {
const date = dateModule.format(new Date(today.setDate(today.getDate() + i)), dateFormat).split(' ')[0];
if (ordersByDates.has(date)) {
weeklyOrders.push(deliveriesCache.get(ordersByDates.get(date)));
}
}
res.status(200).json(weeklyOrders);
return;
};
async function filterOutHolidaysByCountryCode(address: Address, availableTimeSlot: AvailableTimeslots[]): Promise<AvailableTimeslots[]> {
const countryCode = address.code;
const holidays = await getHolidays();
const filteredAvailableTimeSlot = [];
console.log(availableTimeSlot)
holidays.forEach((holiday) => {
if (holiday.country === countryCode) {
availableTimeSlot.forEach((timeSlot) => {
if (timeSlot.start_time.split(' ')[0] !== holiday.date) {
filteredAvailableTimeSlot.push(timeSlot)
}
})
}
});
return filteredAvailableTimeSlot;
}
async function availableTimeSlots(address: Address): Promise<AvailableTimeslots[]> {
const availableTimeSlot = [];
const timeslots = await getAvailableTimeSlots();
// check by postcode if any available timeslots
// the time needs to be in "2023-09-18 14:00:00" format
for (const timeslot of timeslots.courier_available_timeslots) {
if (timeslot.supported_postcodes.includes(address.postcode)) {
availableTimeSlot.push({
id: timeslot.id,
start_time: timeslot.start_time,
end_time: timeslot.end_time
});
}
}
return availableTimeSlot;
}