208 lines
6.5 KiB
TypeScript
208 lines
6.5 KiB
TypeScript
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 today’s 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;
|
||
}
|