Compare commits
No commits in common. "6b68ff1f227b6ea65f8fa2bc505d619d80bc74db" and "fee7f1a266fc85c5864c1ddadd656756b205637b" have entirely different histories.
6b68ff1f22
...
fee7f1a266
17 changed files with 122 additions and 335 deletions
16
README.md
16
README.md
|
@ -17,22 +17,6 @@ install pg MacOS -
|
||||||
|
|
||||||
Start Postgres -
|
Start Postgres -
|
||||||
brew services start postgresql
|
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
|
knex for migration files
|
||||||
|
|
1
data/holidays.json
Normal file
1
data/holidays.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"holidays":["2023-05-01","2023-06-12","2023-09-25"]}
|
1
data/timeSlots.json
Normal file
1
data/timeSlots.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"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,16 +13,14 @@
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"axios": "^1.3.6",
|
"axios": "^1.3.6",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"date-and-time": "^3.0.0",
|
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"knex": "^2.4.2"
|
"knex": "^2.4.2",
|
||||||
|
"pg": "^8.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^18.16.0",
|
"@types/node": "^18.16.0",
|
||||||
"@types/pg": "^8.6.6",
|
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"pg": "^8.10.0",
|
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
}
|
}
|
||||||
|
@ -136,17 +134,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.0.tgz",
|
||||||
"integrity": "sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ=="
|
"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": {
|
"node_modules/@types/qs": {
|
||||||
"version": "6.9.7",
|
"version": "6.9.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
|
||||||
|
@ -326,7 +313,6 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
|
||||||
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
|
@ -446,11 +432,6 @@
|
||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/debug": {
|
||||||
"version": "2.6.9",
|
"version": "2.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||||
|
@ -1214,8 +1195,7 @@
|
||||||
"node_modules/packet-reader": {
|
"node_modules/packet-reader": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
|
"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": {
|
"node_modules/parseurl": {
|
||||||
"version": "1.3.3",
|
"version": "1.3.3",
|
||||||
|
@ -1248,7 +1228,6 @@
|
||||||
"version": "8.10.0",
|
"version": "8.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg/-/pg-8.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg/-/pg-8.10.0.tgz",
|
||||||
"integrity": "sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==",
|
"integrity": "sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer-writer": "2.0.0",
|
"buffer-writer": "2.0.0",
|
||||||
"packet-reader": "1.0.0",
|
"packet-reader": "1.0.0",
|
||||||
|
@ -1279,7 +1258,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
|
||||||
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
|
@ -1288,7 +1266,6 @@
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.0.tgz",
|
||||||
"integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==",
|
"integrity": "sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==",
|
||||||
"dev": true,
|
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"pg": ">=8.0"
|
"pg": ">=8.0"
|
||||||
}
|
}
|
||||||
|
@ -1296,14 +1273,12 @@
|
||||||
"node_modules/pg-protocol": {
|
"node_modules/pg-protocol": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz",
|
"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": {
|
"node_modules/pg-types": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
|
||||||
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-int8": "1.0.1",
|
"pg-int8": "1.0.1",
|
||||||
"postgres-array": "~2.0.0",
|
"postgres-array": "~2.0.0",
|
||||||
|
@ -1319,7 +1294,6 @@
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
|
||||||
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
"integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"split2": "^4.1.0"
|
"split2": "^4.1.0"
|
||||||
}
|
}
|
||||||
|
@ -1340,7 +1314,6 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||||
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
|
@ -1349,7 +1322,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
|
||||||
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
"integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
@ -1358,7 +1330,6 @@
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
|
||||||
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
@ -1367,7 +1338,6 @@
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
|
||||||
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"xtend": "^4.0.0"
|
"xtend": "^4.0.0"
|
||||||
},
|
},
|
||||||
|
@ -1630,7 +1600,6 @@
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
|
||||||
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 10.x"
|
"node": ">= 10.x"
|
||||||
}
|
}
|
||||||
|
@ -1901,7 +1870,6 @@
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
|
||||||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.4"
|
"node": ">=0.4"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,13 @@
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
"axios": "^1.3.6",
|
"axios": "^1.3.6",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"date-and-time": "^3.0.0",
|
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"knex": "^2.4.2"
|
"knex": "^2.4.2",
|
||||||
|
"pg": "^8.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"pg": "^8.10.0",
|
|
||||||
"@types/node": "^18.16.0",
|
"@types/node": "^18.16.0",
|
||||||
"@types/pg": "^8.6.6",
|
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
|
|
|
@ -1,17 +1,7 @@
|
||||||
[
|
{
|
||||||
{
|
"holidays": [
|
||||||
"name": "New Year's Day",
|
"2023-05-01",
|
||||||
"date": "2023-01-17",
|
"2023-06-12",
|
||||||
"country": "US"
|
"2023-09-25"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Mother's Day",
|
|
||||||
"date": "2023-09-18",
|
|
||||||
"country": "US"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Independence Day",
|
|
||||||
"date": "2023-09-17",
|
|
||||||
"country": "GB"
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
}
|
|
@ -1,43 +1,10 @@
|
||||||
{
|
{
|
||||||
"courier_available_timeslots": [
|
"timeSlots": [
|
||||||
{
|
"08:00 - 10:00",
|
||||||
"id": 1,
|
"10:00 - 12:00",
|
||||||
"start_time": "2023-09-17 08:00:00",
|
"12:00 - 14:00",
|
||||||
"end_time": "2023-09-17 09:00:00",
|
"14:00 - 16:00",
|
||||||
"supported_postcodes": [
|
"16:00 - 18:00",
|
||||||
"W1H 1LJ",
|
"18:00 - 20:00"
|
||||||
"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,14 +13,12 @@ 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}`);
|
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) {
|
if (response.data.results.length > 0) {
|
||||||
const result = response.data.results[0];
|
const result = response.data.results[0];
|
||||||
console.log(result)
|
|
||||||
return {
|
return {
|
||||||
country: result.country,
|
country: result.country,
|
||||||
street: result.street,
|
street: result.street,
|
||||||
line1: result.address_line1,
|
line1: result.address_line1,
|
||||||
line2: result.address_line2,
|
line2: result.address_line2,
|
||||||
postcode: result.postcode,
|
postcode: result.postcode
|
||||||
code: result.country_code.toUpperCase()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No results found');
|
throw new Error('No results found');
|
||||||
|
|
207
src/handlers.ts
207
src/handlers.ts
|
@ -1,208 +1,37 @@
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import dateModule from 'date-and-time';
|
|
||||||
|
|
||||||
import { resolveAddress } from './geocoding';
|
import { resolveAddress } from './geocoding';
|
||||||
import { Address, AvailableTimeslots, Orders } from './types';
|
import { Address } from './types';
|
||||||
import { randomUUID } from 'crypto';
|
// DOME
|
||||||
import { getAvailableTimeSlots } from './services/timeslotsService';
|
import { getAvailableTimeSlots } from './services/timeslotsService';
|
||||||
import { getHolidays } from './services/holidaysService';
|
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) => {
|
export const resolveAddressHandler = (req: Request, res: Response) => {
|
||||||
if (!req.body.searchTerm) {
|
console.info("resolveAddressHandler called");
|
||||||
res.status(400).json({ error: 'Missing searchTerm' });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const address: Promise<Address> = resolveAddress(req.body.searchTerm);
|
const address: Promise<Address> = resolveAddress(req.body.searchTerm);
|
||||||
address.then((result) => {
|
address.then((result) => {
|
||||||
|
console.info("resolveAddressHandler result: ", result);
|
||||||
res.status(200).json(result);
|
res.status(200).json(result);
|
||||||
return;
|
})
|
||||||
})
|
};
|
||||||
};
|
|
||||||
|
|
||||||
export const timeslotsHandler = async (req: Request, res: Response): Promise<void> => {
|
export const timeslotsHandler = (req: Request, res: Response) => {
|
||||||
if (!req.body.address) {
|
// TODO: Implement timeslots functionality
|
||||||
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 deliveriesHandler = (req: Request, res: Response) => {
|
||||||
|
// TODO: Implement deliveries functionality
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dailyDeliveriesHandler = (req: Request, res: Response): void => {
|
export const cancelDeliveryHandler = (req: Request, res: Response) => {
|
||||||
|
// TODO: Implement cancel delivery functionality
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dailyDeliveriesHandler = (req: Request, res: Response) => {
|
||||||
// TODO: Implement daily deliveries functionality
|
// 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 => {
|
export const weeklyDeliveriesHandler = (req: Request, res: Response) => {
|
||||||
// TODO: Implement weekly deliveries functionality
|
// 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;
|
|
||||||
}
|
|
44
src/index.ts
44
src/index.ts
|
@ -1,13 +1,47 @@
|
||||||
// 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';
|
import app from './app';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
const env = process.env.NODE_ENV || 'development';
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
app.listen(PORT, () => {
|
app.listen(PORT, () => {
|
||||||
console.log("STARTING ... ")
|
console.log("STARTING ... ")
|
||||||
|
if (env === 'development') {
|
||||||
|
console.log("Generating mock data files")
|
||||||
|
generateMockDataFiles();
|
||||||
|
}
|
||||||
console.log(`Server is running on port ${PORT}`);
|
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) {
|
exports.up = function(knex) {
|
||||||
return knex.schema.createTable('orders', function(table) {
|
return knex.schema.createTable('timeslots', function(table) {
|
||||||
table.string('_id', 36).primary();
|
table.increments('id').primary();
|
||||||
table.string('userId', 36).notNullable();
|
table.timestamp('start_time').notNullable();
|
||||||
table.string('slotId', 36).notNullable();
|
table.timestamp('end_time').notNullable();
|
||||||
table.string('deliveryCreatedDate', 255).notNullable();
|
table.boolean('is_reserved').notNullable().defaultTo(false);
|
||||||
|
table.timestamps(true, true);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
exports.down = function(knex) {
|
exports.down = function(knex) {
|
||||||
return knex.schema.dropTable('orders');
|
return knex.schema.dropTable('timeslots');
|
||||||
};
|
};
|
|
@ -4,10 +4,10 @@ import { resolveAddressHandler, timeslotsHandler, deliveriesHandler, cancelDeliv
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.post('/resolve-address', resolveAddressHandler);
|
router.post('/resolve-address', resolveAddressHandler);
|
||||||
router.delete('/deliveries/:deliveryId', cancelDeliveryHandler);
|
|
||||||
router.post('/timeslots', timeslotsHandler);
|
|
||||||
router.get('/deliveries/daily', dailyDeliveriesHandler);
|
router.get('/deliveries/daily', dailyDeliveriesHandler);
|
||||||
router.get('/deliveries/weekly', weeklyDeliveriesHandler);
|
router.get('/deliveries/weekly', weeklyDeliveriesHandler);
|
||||||
|
router.post('/timeslots', timeslotsHandler);
|
||||||
router.post('/deliveries', deliveriesHandler);
|
router.post('/deliveries', deliveriesHandler);
|
||||||
|
router.delete('/deliveries/:id', cancelDeliveryHandler);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
30
src/seed/load-data.js
Normal file
30
src/seed/load-data.js
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -1,7 +1,6 @@
|
||||||
// for demo resolving the promise immediately with mock data
|
// 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';
|
import timeSlots from '../data/timeslots.json';
|
||||||
|
|
||||||
export function getAvailableTimeSlots() {
|
export function getAvailableTimeSlots() {
|
||||||
return Promise.resolve(timeSlots);
|
return Promise.resolve(timeSlots);
|
||||||
}
|
}
|
2
src/types/index.js
Normal file
2
src/types/index.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@ -4,18 +4,4 @@ export interface Address {
|
||||||
line2: string;
|
line2: string;
|
||||||
country: string;
|
country: string;
|
||||||
postcode: 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