2023-04-24 17:27:36 +00:00
|
|
|
|
import { Request, Response } from 'express';
|
2023-04-26 17:41:38 +00:00
|
|
|
|
import dateModule from 'date-and-time';
|
|
|
|
|
|
2023-04-24 18:25:01 +00:00
|
|
|
|
import { resolveAddress } from './geocoding';
|
2023-04-26 17:51:14 +00:00
|
|
|
|
import { Address, AvailableTimeslots, Orders } from './types';
|
2023-04-26 16:32:22 +00:00
|
|
|
|
import { randomUUID } from 'crypto';
|
2023-04-24 23:00:39 +00:00
|
|
|
|
import { getAvailableTimeSlots } from './services/timeslotsService';
|
|
|
|
|
import { getHolidays } from './services/holidaysService';
|
2023-04-24 17:27:36 +00:00
|
|
|
|
|
2023-04-26 17:04:31 +00:00
|
|
|
|
const dateFormat = 'YYYY-MM-DD HH:MM:SS';
|
2023-04-26 16:32:22 +00:00
|
|
|
|
|
2023-04-26 17:41:38 +00:00
|
|
|
|
// create a hashing for caching delivery slots.
|
2023-04-26 16:32:22 +00:00
|
|
|
|
const deliveriesCache = new Map();
|
2023-04-26 17:41:38 +00:00
|
|
|
|
// create a hashing for caching timeslots.
|
2023-04-26 16:32:22 +00:00
|
|
|
|
const slotsInUse = new Map();
|
2023-04-26 17:41:38 +00:00
|
|
|
|
// orders by dates caching.
|
|
|
|
|
const ordersByDates = new Map();
|
2023-04-26 16:32:22 +00:00
|
|
|
|
|
2023-04-24 17:27:36 +00:00
|
|
|
|
export const resolveAddressHandler = (req: Request, res: Response) => {
|
2023-04-26 10:35:39 +00:00
|
|
|
|
if (!req.body.searchTerm) {
|
|
|
|
|
res.status(400).json({ error: 'Missing searchTerm' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-04-24 18:49:20 +00:00
|
|
|
|
const address: Promise<Address> = resolveAddress(req.body.searchTerm);
|
2023-04-24 18:25:01 +00:00
|
|
|
|
address.then((result) => {
|
|
|
|
|
res.status(200).json(result);
|
2023-04-26 17:45:05 +00:00
|
|
|
|
return;
|
2023-04-26 10:35:39 +00:00
|
|
|
|
})
|
|
|
|
|
};
|
2023-04-24 17:27:36 +00:00
|
|
|
|
|
2023-04-26 17:51:14 +00:00
|
|
|
|
export const timeslotsHandler = async (req: Request, res: Response): Promise<void> => {
|
2023-04-26 10:35:39 +00:00
|
|
|
|
if (!req.body.address) {
|
|
|
|
|
res.status(400).json({ error: 'Missing address' });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const address: Address = req.body.address;
|
|
|
|
|
const timeSlots: AvailableTimeslots[] = await availableTimeSlots(address);
|
2023-04-26 16:32:22 +00:00
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-04-26 10:35:39 +00:00
|
|
|
|
res.status(200).json(availableTimeSlotsResult);
|
2023-04-26 17:45:05 +00:00
|
|
|
|
return;
|
2023-04-24 17:27:36 +00:00
|
|
|
|
};
|
2023-04-26 10:35:39 +00:00
|
|
|
|
|
2023-04-26 17:51:14 +00:00
|
|
|
|
export const deliveriesHandler = (req: Request, res: Response): void => {
|
2023-04-26 16:32:22 +00:00
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 17:04:31 +00:00
|
|
|
|
// Idea: check if user has already booked a delivery - is this needed?
|
2023-04-26 16:32:22 +00:00
|
|
|
|
// 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
|
2023-04-26 17:04:31 +00:00
|
|
|
|
// the date needs to be in "2023-09-18 14:00:00" format
|
2023-04-26 17:41:38 +00:00
|
|
|
|
const deliveryCreatedDate = dateModule.format(new Date(), dateFormat);
|
|
|
|
|
const date = deliveryCreatedDate.split(' ')[0];
|
2023-04-26 17:04:31 +00:00
|
|
|
|
|
2023-04-26 16:32:22 +00:00
|
|
|
|
const delivery = {
|
|
|
|
|
_id: deliveryId,
|
|
|
|
|
userId,
|
|
|
|
|
slotId,
|
2023-04-26 17:41:38 +00:00
|
|
|
|
deliveryCreatedDate: deliveryCreatedDate
|
2023-04-26 16:32:22 +00:00
|
|
|
|
};
|
2023-04-26 17:41:38 +00:00
|
|
|
|
|
|
|
|
|
// INFO: This is a very simple implementation of caching.
|
|
|
|
|
// INFO: In my opinion, transaction should be used to avoid data inconsistency.
|
|
|
|
|
|
2023-04-26 16:32:22 +00:00
|
|
|
|
if (slotsInUse.has(slotId)) {
|
|
|
|
|
slotsInUse.get(slotId).push(deliveryId);
|
|
|
|
|
} else {
|
|
|
|
|
slotsInUse.set(slotId, [deliveryId]);
|
|
|
|
|
}
|
2023-04-26 17:41:38 +00:00
|
|
|
|
|
|
|
|
|
if (ordersByDates.has(date)) {
|
|
|
|
|
ordersByDates.get(date).push(deliveryId);
|
|
|
|
|
} else {
|
|
|
|
|
ordersByDates.set(date, [deliveryId]);
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-26 16:32:22 +00:00
|
|
|
|
deliveriesCache.set(deliveryId, delivery);
|
|
|
|
|
|
2023-04-26 17:41:38 +00:00
|
|
|
|
console.log("ordersByDates", ordersByDates)
|
2023-04-26 16:32:22 +00:00
|
|
|
|
console.log("deliveriesCache", deliveriesCache)
|
|
|
|
|
console.log("slotsInUse", slotsInUse)
|
2023-04-26 17:41:38 +00:00
|
|
|
|
|
2023-04-26 16:32:22 +00:00
|
|
|
|
res.status(200).json(delivery);
|
2023-04-26 17:45:05 +00:00
|
|
|
|
return;
|
2023-04-24 17:27:36 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-04-26 16:32:22 +00:00
|
|
|
|
|
2023-04-26 17:51:14 +00:00
|
|
|
|
export const cancelDeliveryHandler = (req: Request, res: Response): void => {
|
2023-04-24 17:27:36 +00:00
|
|
|
|
// TODO: Implement cancel delivery functionality
|
2023-04-26 16:32:22 +00:00
|
|
|
|
// 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);
|
|
|
|
|
}
|
2023-04-26 17:45:05 +00:00
|
|
|
|
|
2023-04-26 16:32:22 +00:00
|
|
|
|
res.status(200).json(delivery);
|
2023-04-26 17:45:05 +00:00
|
|
|
|
return;
|
2023-04-24 17:27:36 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-04-26 17:51:14 +00:00
|
|
|
|
export const dailyDeliveriesHandler = (req: Request, res: Response): void => {
|
2023-04-24 17:27:36 +00:00
|
|
|
|
// TODO: Implement daily deliveries functionality
|
2023-04-26 17:41:38 +00:00
|
|
|
|
// GET /deliveries/daily - retrieve all today’s deliveries - by today in ordersByDates
|
|
|
|
|
const date = dateModule.format(new Date(), dateFormat).split(' ')[0];
|
2023-04-26 17:51:14 +00:00
|
|
|
|
const todaysOrders: Orders[] = [];
|
2023-04-26 17:41:38 +00:00
|
|
|
|
if (ordersByDates.has(date)) {
|
|
|
|
|
ordersByDates.get(date).forEach((orderId) => {
|
|
|
|
|
todaysOrders.push(deliveriesCache.get(orderId));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
res.status(200).json(todaysOrders);
|
2023-04-26 17:45:05 +00:00
|
|
|
|
return;
|
2023-04-24 17:27:36 +00:00
|
|
|
|
};
|
|
|
|
|
|
2023-04-26 17:51:14 +00:00
|
|
|
|
export const weeklyDeliveriesHandler = (req: Request, res: Response): void => {
|
2023-04-24 17:27:36 +00:00
|
|
|
|
// TODO: Implement weekly deliveries functionality
|
2023-04-26 17:41:38 +00:00
|
|
|
|
// 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();
|
2023-04-26 17:51:14 +00:00
|
|
|
|
const weeklyOrders: Orders [] = [];
|
2023-04-26 17:41:38 +00:00
|
|
|
|
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);
|
2023-04-26 17:45:05 +00:00
|
|
|
|
return;
|
2023-04-24 17:27:36 +00:00
|
|
|
|
};
|
2023-04-26 10:35:39 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-04-26 16:32:22 +00:00
|
|
|
|
async function filterOutHolidaysByCountryCode(address: Address, availableTimeSlot: AvailableTimeslots[]): Promise<AvailableTimeslots[]> {
|
2023-04-26 10:35:39 +00:00
|
|
|
|
const countryCode = address.code;
|
|
|
|
|
const holidays = await getHolidays();
|
|
|
|
|
|
|
|
|
|
const filteredAvailableTimeSlot = [];
|
|
|
|
|
|
|
|
|
|
console.log(availableTimeSlot)
|
|
|
|
|
holidays.forEach((holiday) => {
|
2023-04-26 16:32:22 +00:00
|
|
|
|
if (holiday.country === countryCode) {
|
2023-04-26 10:35:39 +00:00
|
|
|
|
availableTimeSlot.forEach((timeSlot) => {
|
2023-04-26 16:32:22 +00:00
|
|
|
|
if (timeSlot.start_time.split(' ')[0] !== holiday.date) {
|
2023-04-26 10:35:39 +00:00
|
|
|
|
filteredAvailableTimeSlot.push(timeSlot)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return filteredAvailableTimeSlot;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-04-26 17:41:38 +00:00
|
|
|
|
async function availableTimeSlots(address: Address): Promise<AvailableTimeslots[]> {
|
2023-04-26 10:35:39 +00:00
|
|
|
|
const availableTimeSlot = [];
|
|
|
|
|
const timeslots = await getAvailableTimeSlots();
|
|
|
|
|
// check by postcode if any available timeslots
|
2023-04-26 17:04:31 +00:00
|
|
|
|
// the time needs to be in "2023-09-18 14:00:00" format
|
2023-04-26 10:35:39 +00:00
|
|
|
|
for (const timeslot of timeslots.courier_available_timeslots) {
|
|
|
|
|
if (timeslot.supported_postcodes.includes(address.postcode)) {
|
|
|
|
|
availableTimeSlot.push({
|
2023-04-26 16:32:22 +00:00
|
|
|
|
id: timeslot.id,
|
2023-04-26 10:35:39 +00:00
|
|
|
|
start_time: timeslot.start_time,
|
|
|
|
|
end_time: timeslot.end_time
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return availableTimeSlot;
|
2023-04-26 17:04:31 +00:00
|
|
|
|
}
|