Compare commits
10 commits
fee7f1a266
...
6b68ff1f22
Author | SHA1 | Date | |
---|---|---|---|
6b68ff1f22 | |||
98a3771516 | |||
b92a569e04 | |||
418b64ad9a | |||
3e679c473a | |||
9e437999f7 | |||
931d41d04c | |||
e562e3f590 | |||
4cdafde55c | |||
53dd3ada4f |
17 changed files with 336 additions and 123 deletions
16
README.md
16
README.md
|
@ -18,6 +18,22 @@ install pg MacOS -
|
|||
Start Postgres -
|
||||
brew services start postgresql
|
||||
|
||||
Postgres CLI -
|
||||
psql postgres
|
||||
|
||||
init POSTGRES Database -
|
||||
CREATE DATABASE drop_shopping;
|
||||
|
||||
create tables -
|
||||
CREATE TABLE deliveries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL,
|
||||
slot_id INTEGER NOT NULL,
|
||||
delivery_date DATE NOT NULL,
|
||||
address TEXT NOT NULL,
|
||||
status VARCHAR(10) NOT NULL
|
||||
);
|
||||
|
||||
|
||||
knex for migration files
|
||||
npx knex migrate:make create_addresses_table
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{"holidays":["2023-05-01","2023-06-12","2023-09-25"]}
|
|
@ -1 +0,0 @@
|
|||
{"timeSlots":["08:00 - 10:00","10:00 - 12:00","12:00 - 14:00","14:00 - 16:00","16:00 - 18:00","18:00 - 20:00"]}
|
40
package-lock.json
generated
40
package-lock.json
generated
|
@ -13,14 +13,16 @@
|
|||
"@types/express": "^4.17.17",
|
||||
"axios": "^1.3.6",
|
||||
"body-parser": "^1.20.2",
|
||||
"date-and-time": "^3.0.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"knex": "^2.4.2",
|
||||
"pg": "^8.10.0"
|
||||
"knex": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/pg": "^8.6.6",
|
||||
"nodemon": "^2.0.22",
|
||||
"pg": "^8.10.0",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.0.4"
|
||||
}
|
||||
|
@ -134,6 +136,17 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz",
|
||||
"integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ=="
|
||||
},
|
||||
"node_modules/@types/pg": {
|
||||
"version": "8.6.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz",
|
||||
"integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"pg-protocol": "*",
|
||||
"pg-types": "^2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
|
||||
|
@ -313,6 +326,7 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
|
@ -432,6 +446,11 @@
|
|||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/date-and-time": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.0.0.tgz",
|
||||
"integrity": "sha512-uuzXp/mvv6jEMLiP5QzERSQPzHqYnv9i8NZ8BS5kYeB2sakv74EewQiCS4Ahxwq3In+9fYZhGztuDHRVzIOkFQ=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
|
@ -1195,7 +1214,8 @@
|
|||
"node_modules/packet-reader": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
||||
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
|
||||
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/parseurl": {
|
||||
"version": "1.3.3",
|
||||
|
@ -1228,6 +1248,7 @@
|
|||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.10.0.tgz",
|
||||
"integrity": "sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"buffer-writer": "2.0.0",
|
||||
"packet-reader": "1.0.0",
|
||||
|
@ -1258,6 +1279,7 @@
|
|||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
|
@ -1266,6 +1288,7 @@
|
|||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz",
|
||||
"integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==",
|
||||
"dev": true,
|
||||
"peerDependencies": {
|
||||
"pg": ">=8.0"
|
||||
}
|
||||
|
@ -1273,12 +1296,14 @@
|
|||
"node_modules/pg-protocol": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
||||
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q=="
|
||||
"integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/pg-types": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"pg-int8": "1.0.1",
|
||||
"postgres-array": "~2.0.0",
|
||||
|
@ -1294,6 +1319,7 @@
|
|||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"split2": "^4.1.0"
|
||||
}
|
||||
|
@ -1314,6 +1340,7 @@
|
|||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
|
@ -1322,6 +1349,7 @@
|
|||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -1330,6 +1358,7 @@
|
|||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
|
@ -1338,6 +1367,7 @@
|
|||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"xtend": "^4.0.0"
|
||||
},
|
||||
|
@ -1600,6 +1630,7 @@
|
|||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.x"
|
||||
}
|
||||
|
@ -1870,6 +1901,7 @@
|
|||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
|
|
|
@ -15,13 +15,15 @@
|
|||
"@types/express": "^4.17.17",
|
||||
"axios": "^1.3.6",
|
||||
"body-parser": "^1.20.2",
|
||||
"date-and-time": "^3.0.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"knex": "^2.4.2",
|
||||
"pg": "^8.10.0"
|
||||
"knex": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"pg": "^8.10.0",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/pg": "^8.6.6",
|
||||
"nodemon": "^2.0.22",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"typescript": "^5.0.4"
|
||||
|
|
|
@ -1,7 +1,17 @@
|
|||
{
|
||||
"holidays": [
|
||||
"2023-05-01",
|
||||
"2023-06-12",
|
||||
"2023-09-25"
|
||||
[
|
||||
{
|
||||
"name": "New Year's Day",
|
||||
"date": "2023-01-17",
|
||||
"country": "US"
|
||||
},
|
||||
{
|
||||
"name": "Mother's Day",
|
||||
"date": "2023-09-18",
|
||||
"country": "US"
|
||||
},
|
||||
{
|
||||
"name": "Independence Day",
|
||||
"date": "2023-09-17",
|
||||
"country": "GB"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,10 +1,43 @@
|
|||
{
|
||||
"timeSlots": [
|
||||
"08:00 - 10:00",
|
||||
"10:00 - 12:00",
|
||||
"12:00 - 14:00",
|
||||
"14:00 - 16:00",
|
||||
"16:00 - 18:00",
|
||||
"18:00 - 20:00"
|
||||
"courier_available_timeslots": [
|
||||
{
|
||||
"id": 1,
|
||||
"start_time": "2023-09-17 08:00:00",
|
||||
"end_time": "2023-09-17 09:00:00",
|
||||
"supported_postcodes": [
|
||||
"W1H 1LJ",
|
||||
"2222222"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"start_time": "2023-09-17 09:00:00",
|
||||
"end_time": "2023-09-17 10:00:00",
|
||||
"supported_postcodes": [
|
||||
"1111111",
|
||||
"2222222",
|
||||
"3333333"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"start_time": "2023-09-17 09:00:00",
|
||||
"end_time": "2023-09-17 10:00:00",
|
||||
"supported_postcodes": [
|
||||
"4444444",
|
||||
"5555555",
|
||||
"6666666"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"start_time": "2023-04-26 14:00:00",
|
||||
"end_time": "2023-04-26 15:00:00",
|
||||
"supported_postcodes": [
|
||||
"W1H 1LJ",
|
||||
"5555555",
|
||||
"6666666"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -13,12 +13,14 @@ export const resolveAddress = async (searchTerm: string): Promise<Address> => {
|
|||
const response = await axios.get(`https://api.geoapify.com/v1/geocode/search?text=${searchTerm}&format=json&apiKey=${GEOCODING_API_KEY}`);
|
||||
if (response.data.results.length > 0) {
|
||||
const result = response.data.results[0];
|
||||
console.log(result)
|
||||
return {
|
||||
country: result.country,
|
||||
street: result.street,
|
||||
line1: result.address_line1,
|
||||
line2: result.address_line2,
|
||||
postcode: result.postcode
|
||||
postcode: result.postcode,
|
||||
code: result.country_code.toUpperCase()
|
||||
}
|
||||
} else {
|
||||
throw new Error('No results found');
|
||||
|
|
195
src/handlers.ts
195
src/handlers.ts
|
@ -1,37 +1,208 @@
|
|||
import { Request, Response } from 'express';
|
||||
import dateModule from 'date-and-time';
|
||||
|
||||
import { resolveAddress } from './geocoding';
|
||||
import { Address } from './types';
|
||||
// DOME
|
||||
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) => {
|
||||
console.info("resolveAddressHandler called");
|
||||
if (!req.body.searchTerm) {
|
||||
res.status(400).json({ error: 'Missing searchTerm' });
|
||||
return;
|
||||
}
|
||||
const address: Promise<Address> = resolveAddress(req.body.searchTerm);
|
||||
address.then((result) => {
|
||||
console.info("resolveAddressHandler result: ", result);
|
||||
res.status(200).json(result);
|
||||
return;
|
||||
})
|
||||
};
|
||||
|
||||
export const timeslotsHandler = (req: Request, res: Response) => {
|
||||
// TODO: Implement timeslots functionality
|
||||
|
||||
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) => {
|
||||
// TODO: Implement deliveries functionality
|
||||
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) => {
|
||||
|
||||
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) => {
|
||||
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) => {
|
||||
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;
|
||||
}
|
46
src/index.ts
46
src/index.ts
|
@ -1,47 +1,13 @@
|
|||
import app from './app';
|
||||
import fs from 'fs';
|
||||
// INFO: this is a simple in-memory cache, for demo purposes only. this app will not scale!
|
||||
// INFO: SQL database is required for continuing this application!
|
||||
// INFO: Used Map instead of Object for better performance.
|
||||
// INFO: Making foundation for PostgresSQL database. migration files are in src/migrations
|
||||
|
||||
import app from './app';
|
||||
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const PORT = process.env.PORT || 3000;
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log("STARTING ... ")
|
||||
if (env === 'development') {
|
||||
console.log("Generating mock data files")
|
||||
generateMockDataFiles();
|
||||
}
|
||||
console.log(`Server is running on port ${PORT}`);
|
||||
});
|
||||
|
||||
|
||||
const generateMockDataFiles = () => {
|
||||
|
||||
if (!fs.existsSync('./data')) {
|
||||
fs.mkdirSync('./data');
|
||||
}
|
||||
|
||||
if (!fs.existsSync('./data/timeSlots.json')) {
|
||||
const timeSlots = {
|
||||
timeSlots: [
|
||||
"08:00 - 10:00",
|
||||
"10:00 - 12:00",
|
||||
"12:00 - 14:00",
|
||||
"14:00 - 16:00",
|
||||
"16:00 - 18:00",
|
||||
"18:00 - 20:00"
|
||||
]
|
||||
}
|
||||
fs.writeFileSync('./data/timeSlots.json', JSON.stringify(timeSlots));
|
||||
}
|
||||
|
||||
if (!fs.existsSync('./data/holidays.json')) {
|
||||
const holidays = {
|
||||
holidays: [
|
||||
"2023-05-01",
|
||||
"2023-06-12",
|
||||
"2023-09-25"
|
||||
]
|
||||
}
|
||||
fs.writeFileSync('./data/holidays.json', JSON.stringify(holidays));
|
||||
}
|
||||
}
|
|
@ -1,13 +1,13 @@
|
|||
exports.up = function(knex) {
|
||||
return knex.schema.createTable('timeslots', function(table) {
|
||||
table.increments('id').primary();
|
||||
table.timestamp('start_time').notNullable();
|
||||
table.timestamp('end_time').notNullable();
|
||||
table.boolean('is_reserved').notNullable().defaultTo(false);
|
||||
table.timestamps(true, true);
|
||||
return knex.schema.createTable('orders', function(table) {
|
||||
table.string('_id', 36).primary();
|
||||
table.string('userId', 36).notNullable();
|
||||
table.string('slotId', 36).notNullable();
|
||||
table.string('deliveryCreatedDate', 255).notNullable();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
exports.down = function(knex) {
|
||||
return knex.schema.dropTable('timeslots');
|
||||
return knex.schema.dropTable('orders');
|
||||
};
|
|
@ -4,10 +4,10 @@ import { resolveAddressHandler, timeslotsHandler, deliveriesHandler, cancelDeliv
|
|||
const router = express.Router();
|
||||
|
||||
router.post('/resolve-address', resolveAddressHandler);
|
||||
router.delete('/deliveries/:deliveryId', cancelDeliveryHandler);
|
||||
router.post('/timeslots', timeslotsHandler);
|
||||
router.get('/deliveries/daily', dailyDeliveriesHandler);
|
||||
router.get('/deliveries/weekly', weeklyDeliveriesHandler);
|
||||
router.post('/timeslots', timeslotsHandler);
|
||||
router.post('/deliveries', deliveriesHandler);
|
||||
router.delete('/deliveries/:id', cancelDeliveryHandler);
|
||||
|
||||
export default router;
|
|
@ -1,30 +0,0 @@
|
|||
exports.seed = async function(knex) {
|
||||
// Delete all existing data from the tables
|
||||
// check is table exists first
|
||||
const hasTable = await knex.schema.hasTable('timeslots');
|
||||
if (hasTable) {
|
||||
await knex('timeslots').del();
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Load data from the JSON files
|
||||
const holidays = require('../data/holidays.json');
|
||||
const timeslots = require('../data/timeslots.json');
|
||||
|
||||
// Insert the holiday dates into the holidays table
|
||||
for (const date of holidays) {
|
||||
await knex('holidays').insert({ date });
|
||||
}
|
||||
|
||||
// Insert the time slots into the timeslots table
|
||||
for (const { date, slots } of timeslots) {
|
||||
for (const slot of slots) {
|
||||
await knex('timeslots').insert({
|
||||
date,
|
||||
start_time: slot.start_time,
|
||||
end_time: slot.end_time
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
0
src/seeds/load-data.js
Normal file
0
src/seeds/load-data.js
Normal file
|
@ -1,4 +1,5 @@
|
|||
// for demo resolving the promise immediately with mock data
|
||||
// INFO: assuming this will be replaced with an API call, Will add id to the mock data
|
||||
import timeSlots from '../data/timeslots.json';
|
||||
|
||||
export function getAvailableTimeSlots() {
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@ -4,4 +4,18 @@ export interface Address {
|
|||
line2: string;
|
||||
country: string;
|
||||
postcode: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export interface AvailableTimeslots {
|
||||
id: string;
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
}
|
||||
|
||||
export interface Orders {
|
||||
_id: string;
|
||||
userId: string;
|
||||
slotId: string;
|
||||
deliveryCreatedDate: string;
|
||||
}
|
Loading…
Reference in a new issue