first push

This commit is contained in:
Kfir Dayan 2023-07-08 01:54:57 +03:00
commit 6b7abad013
26 changed files with 2806 additions and 0 deletions

8
.gitignore vendored Normal file
View file

@ -0,0 +1,8 @@
.env
node_modules
dist
init.js
terraform.tfstate
terraform.tfstate.backup
.terraform.lock.hcl
.terraform

65
README.md Normal file
View file

@ -0,0 +1,65 @@
# Project Setup Instructions
This README.md file provides instructions for setting up and running various components of the project, including MongoDB, SQS, NotificationService, and TodoService. Please follow the steps below to get started.
## MongoDB/Docker
1. Install MongoDB if you haven't already.
2. Navigate to the `/infra/docker/docker-compose` directory.
3. Run the following command to start MongoDB using Docker Compose:
```shell
docker-compose up -d
```
## SQS/Terraform
Before proceeding, ensure that you have Terraform installed and have configured AWS CLI with the keys of an authorized user.
1. Navigate to the /infra/terraform directory.
2. Run the following command to initialize Terraform:
```terraform
terraform init
```
3. Run the following command to apply the Terraform configuration:
```terraform
terraform apply
```
## NotificationService
Before running the NotificationService, ensure that you have Node.js, TypeScript (tsc), and npm installed.
Navigate to the /notification-service directory.
Run the following command to install the dependencies:
```npm
npm install
```
Run the following command to build and run (Dem mode)the project:
```npm
npm run dev
```
## TodoService
Before running the TodoService, ensure that you have Node.js, TypeScript (tsc), configured aws-cli and npm installed.
Navigate to the /todo-service directory.
Run the following command to install the dependencies:
```npm
npm install
```
Run the following command to build and run (Dem mode)the project:
```npm
npm run dev
```
## Requests
### Examples are located in request.http - can be run in VSCode with the REST Client extension.

View file

@ -0,0 +1,3 @@
MONGO_INITDB_DATABASE=your-database-name
MONGO_INITDB_ROOT_USERNAME=your-username
MONGO_INITDB_ROOT_PASSWORD=your-password

View file

@ -0,0 +1,19 @@
version: '3.8'
services:
mongodb:
image: arm64v8/mongo:4.0
restart: always
ports:
- 27017:27017
volumes:
- mongodb_vol:/data/db
- ./init-scripts/init.js:/docker-entrypoint-initdb.d/mongo-init.js
environment:
- MONGO_INITDB_DATABASE=${MONGO_INITDB_DATABASE}
- MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD}
platform: linux/arm64/v8
expose:
- 27017
volumes:
mongodb_vol:

View file

@ -0,0 +1,7 @@
db = db.getSiblingDB('user_db');
db.createUser({
user: "user",
pwd: "12345",
roles: [{ role: "readWrite", db: "user_db" }]
});

View file

@ -0,0 +1 @@
requirepass your_password

12
infra/terraform/main.tf Normal file
View file

@ -0,0 +1,12 @@
provider "aws" {
region = "eu-west-1"
}
resource "aws_sqs_queue" "queue" {
name = "todo_queue"
delay_seconds = 0
max_message_size = 262144
message_retention_seconds = 345600
visibility_timeout_seconds = 30
receive_wait_time_seconds = 0
}

739
notification-service/package-lock.json generated Normal file
View file

@ -0,0 +1,739 @@
{
"name": "notification-service",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "notification-service",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"aws-sdk": "^2.1413.0",
"dotenv": "^16.3.1",
"mongodb": "^5.7.0",
"socket.io": "^4.7.1"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
},
"node_modules/@types/cors": {
"version": "2.8.13",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz",
"integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "20.4.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.0.tgz",
"integrity": "sha512-jfT7iTf/4kOQ9S7CHV9BIyRaQqHu67mOjsIQBC3BKZvzvUB6zLxEwJ6sBE3ozcvP8kF6Uk5PXN0Q+c0dfhGX0g=="
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog=="
},
"node_modules/@types/whatwg-url": {
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz",
"integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==",
"dependencies": {
"@types/node": "*",
"@types/webidl-conversions": "*"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/aws-sdk": {
"version": "2.1413.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1413.0.tgz",
"integrity": "sha512-vKpjC7iRwOhgv7P0xw90mVGO//2rqVPJKyYIs7uxLzSV0JzriVD+yqktOu/Hz6/phOmAd1cMIeFgpEC9ynrppg==",
"dependencies": {
"buffer": "4.9.2",
"events": "1.1.1",
"ieee754": "1.1.13",
"jmespath": "0.16.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"util": "^0.12.4",
"uuid": "8.0.0",
"xml2js": "0.5.0"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/bson": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/bson/-/bson-5.4.0.tgz",
"integrity": "sha512-WRZ5SQI5GfUuKnPTNmAYPiKIof3ORXAF4IRU5UcgmivNIon01rWQlw5RUH954dpu8yGL8T59YShVddIPaU/gFA==",
"engines": {
"node": ">=14.20.1"
}
},
"node_modules/buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"dependencies": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/dotenv": {
"version": "16.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/motdotla/dotenv?sponsor=1"
}
},
"node_modules/engine.io": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz",
"integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.1.0",
"ws": "~8.11.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz",
"integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
"integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
"dependencies": {
"is-callable": "^1.1.3"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/get-intrinsic": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz",
"integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-proto": "^1.0.1",
"has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
"dependencies": {
"has-symbols": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/ip": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
"integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==",
"dependencies": {
"call-bind": "^1.0.2",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-generator-function": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz",
"integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==",
"dependencies": {
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-typed-array": {
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
"integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
"dependencies": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-tostringtag": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
"optional": true
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mongodb": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz",
"integrity": "sha512-zm82Bq33QbqtxDf58fLWBwTjARK3NSvKYjyz997KSy6hpat0prjeX/kxjbPVyZY60XYPDNETaHkHJI2UCzSLuw==",
"dependencies": {
"bson": "^5.4.0",
"mongodb-connection-string-url": "^2.6.0",
"socks": "^2.7.1"
},
"engines": {
"node": ">=14.20.1"
},
"optionalDependencies": {
"saslprep": "^1.0.3"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.201.0",
"@mongodb-js/zstd": "^1.1.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=2.3.0 <3",
"snappy": "^7.2.2"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
}
}
},
"node_modules/mongodb-connection-string-url": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz",
"integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==",
"dependencies": {
"@types/whatwg-url": "^8.2.1",
"whatwg-url": "^11.0.0"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="
},
"node_modules/querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==",
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/saslprep": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
"optional": true,
"dependencies": {
"sparse-bitfield": "^3.0.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha512-8I2a3LovHTOpm7NV5yOyO8IHqgVsfK4+UuySrXU8YXkSRX7k6hCV9b3HrkKCr3nMpgj+0bmocaJJWpvp1oc7ZA=="
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socket.io": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.1.tgz",
"integrity": "sha512-W+utHys2w//dhFjy7iQQu9sGd3eokCjGbl2r59tyLqNiJJBdIebn3GAKEXBr3osqHTObJi2die/25bCx2zsaaw==",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.5.0",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
"dependencies": {
"ws": "~8.11.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socks": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
"integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
"dependencies": {
"ip": "^2.0.0",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.13.0",
"npm": ">= 3.0.0"
}
},
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
"optional": true,
"dependencies": {
"memory-pager": "^1.0.2"
}
},
"node_modules/tr46": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz",
"integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==",
"dependencies": {
"punycode": "^2.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/tr46/node_modules/punycode": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"engines": {
"node": ">=6"
}
},
"node_modules/url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
"integrity": "sha512-hzSUW2q06EqL1gKM/a+obYHLIO6ct2hwPuviqTTOcfFVc61UbfJ2Q32+uGL/HCPxKqrdGB5QUwIe7UqlDgwsOQ==",
"dependencies": {
"punycode": "1.3.2",
"querystring": "0.2.0"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
"is-generator-function": "^1.0.7",
"is-typed-array": "^1.1.3",
"which-typed-array": "^1.1.2"
}
},
"node_modules/uuid": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"engines": {
"node": ">=12"
}
},
"node_modules/whatwg-url": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz",
"integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==",
"dependencies": {
"tr46": "^3.0.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/which-typed-array": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
"integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
"dependencies": {
"available-typed-arrays": "^1.0.5",
"call-bind": "^1.0.2",
"for-each": "^0.3.3",
"gopd": "^1.0.1",
"has-tostringtag": "^1.0.0",
"is-typed-array": "^1.1.10"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml2js": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"engines": {
"node": ">=4.0"
}
}
}
}

View file

@ -0,0 +1,18 @@
{
"name": "notification-service",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "tsc && nodemon dist/main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"aws-sdk": "^2.1413.0",
"dotenv": "^16.3.1",
"mongodb": "^5.7.0",
"socket.io": "^4.7.1"
}
}

View file

@ -0,0 +1,122 @@
import aws from 'aws-sdk';
import { ITodo } from '../interfaces/ITodo';
import { MongoDb } from '../mongodb/MongoDb';
const env = require('dotenv').config().parsed;
export class Sqs {
sqs: aws.SQS;
queueUrl: string;
constructor() {
aws.config.update({
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
});
this.sqs = new aws.SQS();
this.queueUrl = env.AWS_SQS_URL + env.AWS_SQS_QUEUE_NAME;
if (this.sqs) {
console.log("SQS connected");
}
}
async create(payload: ITodo, delayTimeForQueue: number) {
let reQueueTime = 0;
if (delayTimeForQueue > 900) {
reQueueTime = delayTimeForQueue - 900;
delayTimeForQueue = 900;
}
const params = {
DelaySeconds: delayTimeForQueue,
MessageAttributes: {},
MessageBody: JSON.stringify({ payload, delayTimeForQueue, reQueueTime }),
QueueUrl: this.queueUrl,
};
try {
const data = await this.sqs.sendMessage(params).promise();
console.log("Message sent to the queue", data.MessageId);
return data;
} catch (error) {
console.log("Error", error);
throw error;
}
}
async startConsumer() {
while (true) {
const message = await this.getNextQueue();
if (message) {
const { payload, delayTimeForQueue, reQueueTime } = JSON.parse(message.Body) as {
payload: ITodo;
delayTimeForQueue: number;
reQueueTime: number;
};
console.log("Received notification:", payload);
if (reQueueTime === 0) {
try {
await MongoDb.updateTodoStatus(payload);
} catch {
await this.create(payload, delayTimeForQueue);
await this.deleteMessage(message.ReceiptHandle);
console.log("Published new queue with delay, THE DB IS DOWN!:", delayTimeForQueue);
}
await this.deleteMessage(message.ReceiptHandle);
} else if (reQueueTime >= 900) {
const newDelayTime = 900;
const newReQueueTime = reQueueTime - 900;
await this.create(payload, newDelayTime);
await this.deleteMessage(message.ReceiptHandle);
console.log("Published new queue with delay:", newDelayTime);
} else {
const newDelayTime = reQueueTime;
await this.create(payload, newDelayTime);
await this.deleteMessage(message.ReceiptHandle);
console.log("Published new queue with delay:", newDelayTime);
}
}
}
}
private getNextQueue = async () => {
const params = {
QueueUrl: this.queueUrl,
MaxNumberOfMessages: 1,
VisibilityTimeout: 30,
WaitTimeSeconds: 20, // Increase the WaitTimeSeconds for long polling
};
try {
const data = await this.sqs.receiveMessage(params).promise();
const message = data.Messages ? data.Messages[0] : null;
return message;
} catch (error) {
console.error("Error retrieving message from SQS:", error);
return null;
}
}
private deleteMessage = async (receiptHandle: string) => {
const params = {
QueueUrl: this.queueUrl,
ReceiptHandle: receiptHandle,
};
try {
await this.sqs.deleteMessage(params).promise();
console.log("Message deleted");
} catch (error) {
console.error("Error deleting message from SQS:", error);
}
}
}

View file

@ -0,0 +1,11 @@
import { ObjectId } from 'mongodb';
export interface ITodo {
_id: ObjectId;
title: string;
description: string;
due_date: Date;
createAt: Date;
updateAt: Date;
status: string;
}

View file

@ -0,0 +1,16 @@
import { Sqs } from './aws/Sqs';
export class NotificationService {
sqs: Sqs;
constructor() {
this.sqs = new Sqs();
}
startListener() {
this.sqs.startConsumer();
}
}
const notificationService = new NotificationService();
notificationService.startListener();

View file

@ -0,0 +1,26 @@
import { MongoClient, ObjectId } from 'mongodb';
import { ITodo } from '../interfaces/ITodo';
const env = require('dotenv').config().parsed;
export class MongoDb {
public static updateTodoStatus = async (todo: ITodo) => {
const client = new MongoClient(env.DATABASE_URL);
try {
await client.connect();
const db = client.db(env.MONGO_DB_NAME);
const todosCollection = db.collection('todos');
const result = await todosCollection.updateOne(
{ _id: new ObjectId(todo._id) },
{ $set: { status: 'completed' } }
);
console.log(`Updated status of Todo ${todo._id} to completed`);
} catch (error) {
console.error("Error updating Todo status:", error);
} finally {
await client.close();
}
}
}

View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "CommonJS",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "es2022",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"noImplicitAny": true,
}
}

29
request.http Normal file
View file

@ -0,0 +1,29 @@
### Get All Request
GET http://localhost:3000/todo/{ID}
### create new request, YYYY-MM-DD HH:mm:ss.ss
POST http://localhost:3000/todo
Content-Type: application/json
{
"title": "Go to the gym",
"description": "Go to the gym at 8pm",
"due_date": "2023-07-08 01:21:00"
}
### update request
PUT http://localhost:3000/todo/{ID}
Content-Type: application/json
{
"title": "test",
"description": "TEST NEW!",
"due_date": "2020-12-12"
}
### delete *ALL* request
DELETE http://localhost:3000/todo/
### delete request
DELETE http://localhost:3000/todo/{ID}

1342
todo-service/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

22
todo-service/package.json Normal file
View file

@ -0,0 +1,22 @@
{
"name": "todo-service",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"dev": "nodemon dist/main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@aws-sdk/types": "^3.357.0",
"aws-sdk": "^2.1413.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"mongoose": "^7.3.1"
},
"devDependencies": {
"@types/express": "^4.17.17"
}
}

View file

@ -0,0 +1,50 @@
import aws from 'aws-sdk';
import { ITodo } from '../schemas/todoSchema';
const env = require('dotenv').config().parsed;
export class Sqs {
sqs: aws.SQS;
queueUrl: string;
constructor() {
aws.config.update({
accessKeyId: env.AWS_ACCESS_KEY_ID,
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
});
this.sqs = new aws.SQS();
this.queueUrl = env.AWS_SQS_URL + env.AWS_SQS_QUEUE_NAME;
if (this.sqs) {
console.log("SQS connected")
}
}
async create(payload: ITodo, delayTimeForQueue: number) {
let reQueueTime = 0;
if (delayTimeForQueue > 900) {
reQueueTime = delayTimeForQueue - 900;
delayTimeForQueue = 900;
}
const params = {
DelaySeconds: delayTimeForQueue,
MessageAttributes: {},
MessageBody: JSON.stringify({ payload, delayTimeForQueue, reQueueTime }),
QueueUrl: this.queueUrl,
};
return new Promise((resolve, reject) => {
this.sqs.sendMessage(params, (err, data) => {
if (err) {
console.log("Error", err);
reject(err);
} else {
console.log("Message sent to the queue", data.MessageId)
resolve(data);
}
});
});
}
}

View file

@ -0,0 +1,75 @@
import { NextFunction, Request, Response } from 'express';
import { ApiError } from '../utils/ApiError';
import { ITodo } from '../schemas/todoSchema';
import { TodoModel } from '../models/todoModel';
import { Sqs } from '../aws/Sqs';
const env = require('dotenv').config().parsed;
export class TodoController {
private todoModel: TodoModel;
queue: Sqs;
constructor() {
this.todoModel = new TodoModel();
this.queue = new Sqs();
}
public getAll = async (req: Request, res: Response, next: NextFunction) => {
const todos: ITodo[] | ApiError = await this.todoModel.findAll();
if (todos instanceof ApiError) {
return next(todos);
}
return res.json(todos);
}
public getOne = async (req: Request, res: Response, next: NextFunction) => {
const todo: ITodo | ApiError = await this.todoModel.findOne(req.params.id);
if (todo instanceof ApiError) {
return next(todo);
}
return res.json(todo);
}
public createOne = async (req: Request, res: Response, next: NextFunction) => {
try {
const todo: ITodo | ApiError = await this.todoModel.create(req.body);
if (todo instanceof ApiError) {
return next(todo);
}
const id = todo._id;
const delayTimeForQueue = Math.floor((new Date(todo.due_date).getTime() - new Date().getTime()) / 1000);
this.queue.create(todo, delayTimeForQueue);
return res.json(todo);
} catch {
const err = new ApiError('Internal server error', 500, 'Internal Server Error');
next(err)
}
}
public updateOne = async (req: Request, res: Response, next: NextFunction) => {
const todo: ITodo | ApiError = await this.todoModel.update(req.body, req.params.id);
if (todo instanceof ApiError) {
return next(todo);
}
return res.json(todo);
}
public deleteOne = async (req: Request, res: Response, next: NextFunction) => {
const { id } = req.params;
const todo: boolean | ApiError = await this.todoModel.remove(id);
if (!todo) {
const error = new ApiError('Todo not found', 404, 'Not Found');
return next(error);
}
return res.json(todo);
}
public removeAll = async (req: Request, res: Response, next: NextFunction) => {
const todos: boolean | ApiError = await this.todoModel.removeAll();
return res.json(todos);
}
}

58
todo-service/src/main.ts Normal file
View file

@ -0,0 +1,58 @@
import express from 'express';
import mongoose from 'mongoose';
import todoRouter from './routes/todoRouter';
import { ApiError } from './utils/ApiError';
const env = require('dotenv').config().parsed;
class TodoApp {
app: express.Application;
constructor() {
this.connectToDB();
}
public run() {
this.app = express();
this.setMiddlewares();
this.setRoutes();
this.startServer();
}
private setRoutes() {
this.app.use('/todo', todoRouter);
this.app.all('*', (req, res, next) => {
const error = new ApiError('Are you lost?', 404, 'Not Found');
next(error)
});
}
private startServer() {
const PORT = env.PORT || 3000;
this.app.listen(PORT, () => {
console.log(`Server started on port ${PORT}`);
});
}
private setMiddlewares() {
this.app.use(express.json());
}
private connectToDB() {
mongoose.connect(env.DATABASE_URL);
const db = mongoose.connection;
// Check for DB connection
db.on('error', () => {
console.error.bind(console, 'MongoDB connection error:')
process.exit(1);
});
db.once('open', () => {
console.log('Connected to MongoDB');
});
}
}
const app = new TodoApp();
app.run();

View file

@ -0,0 +1,30 @@
import { Request, Response, NextFunction } from 'express';
import { ApiError } from '../utils/ApiError';
const createTodoMiddleWare = async (req: Request, res: Response, next: NextFunction) => {
const { title, description, due_date } = req.body;
if (!title || !due_date) {
const error = new ApiError(`${!title ? 'title' : 'due_date'} is required`, 400, 'Bad Request');
return next(error);
}
try {
if(new Date(due_date) < new Date()) {
const error = new ApiError(`due_date must be greater than current date`, 400, 'Bad Request');
return next(error);
}
} catch {
const error = new ApiError(`due_date must be a valid date`, 400, 'Bad Request');
return next(error);
}
if (!description) {
req.body.description = '';
}
next();
}
export {
createTodoMiddleWare
}

View file

@ -0,0 +1,66 @@
import { Todo, ITodo } from '../schemas/todoSchema';
import { ApiError } from '../utils/ApiError';
interface Todo {
title: string,
description: string,
due_date: Date
}
export class TodoModel {
public findAll = async (): Promise<ITodo[]> => {
const todos = await Todo.find();
return todos;
}
public findOne = async (_id: string): Promise<ITodo | ApiError> => {
try {
const todo = await Todo.findById(_id);
if (!todo) {
const error = new ApiError('Todo not found', 404, 'Not Found');
return error;
}
return todo;
} catch {
const error = new ApiError('Internal server error', 500, 'Internal Server Error');
return error;
}
}
public create = async (params: Todo): Promise<ITodo | ApiError> => {
let todo = new Todo(params);
todo = await todo.save();
if (!todo) {
const error = new ApiError('Internal server error', 500, 'Internal Server Error');
return error;
}
return todo;
}
public update = async (params: Todo, _id: string): Promise<ITodo | ApiError> => {
const todo = await Todo.findOne({ _id });
if (!todo) {
const error = new ApiError('Todo not found', 404, 'Not Found');
return error;
}
todo.title = params.title;
todo.description = params.description;
todo.due_date = params.due_date;
await todo.save();
return todo;
}
public remove = async (_id: string): Promise<boolean | ApiError> => {
const result = await Todo.deleteOne({ _id });
return result.deletedCount > 0 ? true : new ApiError('Todo not found', 404, 'Not Found');
}
public removeAll = async (): Promise<boolean> => {
await Todo.deleteMany({});
return true;
}
}

View file

@ -0,0 +1,31 @@
import { Router } from 'express';
import { TodoController } from '../controllers/todoController';
import { createTodoMiddleWare } from '../middleware/createTodoMiddleWare';
class TodoRouter {
router: Router;
todoController: TodoController;
constructor() {
this.router = Router();
this.todoController = new TodoController();
this.setRoutes();
}
private setRoutes() {
this.router.get('/', this.todoController.getAll);
this.router.get('/:id', this.todoController.getOne);
this.router.post('/', createTodoMiddleWare, this.todoController.createOne);
this.router.put('/:id', createTodoMiddleWare, this.todoController.updateOne);
this.router.delete('/:id', this.todoController.deleteOne);
this.router.delete('/', this.todoController.removeAll)
}
public getRouter() {
return this.router;
}
}
export default new TodoRouter().getRouter();

View file

@ -0,0 +1,23 @@
import mongoose, { Schema, Document } from 'mongoose';
interface ITodo extends Document {
title: string;
description: string;
due_date: Date;
createAt: Date;
updateAt: Date;
status: string;
}
const TodoSchema: Schema<ITodo> = new Schema({
title: { type: String, required: true },
description: { type: String, default: '' },
due_date: { type: Date, required: true },
createAt: { type: Date, default: Date.now },
updateAt: { type: Date, default: Date.now },
status: { type: String, enum: ['pending', 'completed'], default: 'pending' },
});
const Todo = mongoose.model<ITodo>('Todo', TodoSchema, 'todos');
export { Todo, ITodo };

View file

@ -0,0 +1,7 @@
class ApiError extends Error {
constructor(message: string, private statusCode: number = 500,private status: string = 'Internal Server Error') {
super(message);
}
}
export { ApiError };

View file

@ -0,0 +1,13 @@
{
"compilerOptions": {
"module": "CommonJS",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"target": "es2022",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
"noImplicitAny": true,
}
}