Compare commits
2 commits
429ddebf02
...
e8265f5fe3
Author | SHA1 | Date | |
---|---|---|---|
e8265f5fe3 | |||
31c60d2d97 |
15 changed files with 95 additions and 51 deletions
18
README.md
18
README.md
|
@ -12,24 +12,6 @@ This README.md file provides instructions for setting up and running various com
|
||||||
docker-compose up -d
|
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
|
## NotificationService
|
||||||
|
|
||||||
Before running the NotificationService, ensure that you have Node.js, TypeScript (tsc), and npm installed.
|
Before running the NotificationService, ensure that you have Node.js, TypeScript (tsc), and npm installed.
|
||||||
|
|
|
@ -9,11 +9,15 @@ services:
|
||||||
- mongodb_vol:/data/db
|
- mongodb_vol:/data/db
|
||||||
- ./mongo-init-scripts/init.js:/docker-entrypoint-initdb.d/mongo-init.js
|
- ./mongo-init-scripts/init.js:/docker-entrypoint-initdb.d/mongo-init.js
|
||||||
platform: linux/arm64/v8
|
platform: linux/arm64/v8
|
||||||
|
environment:
|
||||||
|
- MONGO_INITDB_DATABASE=${MONGO_INITDB_DATABASE}
|
||||||
|
- MONGO_INITDB_ROOT_USERNAME=${MONGO_INITDB_ROOT_USERNAME}
|
||||||
|
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_INITDB_ROOT_PASSWORD}
|
||||||
expose:
|
expose:
|
||||||
- 27017
|
- 27017
|
||||||
|
|
||||||
rabbitmq:
|
rabbitmq:
|
||||||
image: kfda89/todorabbit:latest
|
image: todorabbit:latest
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- 5672:5672
|
- 5672:5672
|
||||||
|
@ -21,6 +25,9 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- rabbitmq_volume:/var/lib/rabbitmq
|
- rabbitmq_volume:/var/lib/rabbitmq
|
||||||
- ./rabbitmq-init-scripts/init.sh:/docker-entrypoint-initdb.d/init.sh
|
- ./rabbitmq-init-scripts/init.sh:/docker-entrypoint-initdb.d/init.sh
|
||||||
|
environment:
|
||||||
|
- RABBITMQ_DEFAULT_USER=${RABBITMQ_DEFAULT_USER}
|
||||||
|
- RABBITMQ_DEFAULT_PASS=${RABBITMQ_DEFAULT_PASS}
|
||||||
expose:
|
expose:
|
||||||
- 5672
|
- 5672
|
||||||
- 15672
|
- 15672
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
20
notification-service/package-lock.json
generated
20
notification-service/package-lock.json
generated
|
@ -11,6 +11,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"amqplib": "^0.10.3",
|
"amqplib": "^0.10.3",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
"moment-timezone": "^0.5.43",
|
||||||
"mongodb": "^5.7.0"
|
"mongodb": "^5.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -133,6 +134,25 @@
|
||||||
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/moment": {
|
||||||
|
"version": "2.29.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
||||||
|
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/moment-timezone": {
|
||||||
|
"version": "0.5.43",
|
||||||
|
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz",
|
||||||
|
"integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mongodb": {
|
"node_modules/mongodb": {
|
||||||
"version": "5.7.0",
|
"version": "5.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.7.0.tgz",
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"amqplib": "^0.10.3",
|
"amqplib": "^0.10.3",
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
"moment-timezone": "^0.5.43",
|
||||||
"mongodb": "^5.7.0"
|
"mongodb": "^5.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import { ITodo } from "./interfaces/ITodo";
|
import { ITodo } from "./interfaces/ITodo";
|
||||||
import { RabbitMQ } from "./rabbitmq/RabbitMQ";
|
import { RabbitMQ } from "./rabbitmq/RabbitMQ";
|
||||||
import { MongoDbModel } from "./mongodb/MongoDb";
|
import { MongoDbModel } from "./mongodb/MongoDb";
|
||||||
|
import { DateService } from "./services/DateService";
|
||||||
|
|
||||||
export class NotificationService {
|
export class NotificationService {
|
||||||
rabbitmq: RabbitMQ;
|
rabbitmq: RabbitMQ;
|
||||||
mongoModel: MongoDbModel;
|
mongoModel: MongoDbModel;
|
||||||
|
currentDate: any;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.currentDate = DateService.getInstance();
|
||||||
this.rabbitmq = new RabbitMQ();
|
this.rabbitmq = new RabbitMQ();
|
||||||
this.mongoModel = new MongoDbModel();
|
this.mongoModel = new MongoDbModel();
|
||||||
this.startListener();
|
this.startListener();
|
||||||
|
@ -20,13 +23,17 @@ export class NotificationService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async newMessageValidator(message: ITodo) {
|
async newMessageValidator(message: ITodo) {
|
||||||
const todo = await this.mongoModel.getTodoById(message._id.toString());
|
try {
|
||||||
if (todo) {
|
const todo = await this.mongoModel.getTodoById(message._id.toString());
|
||||||
const due_date = new Date(todo.due_date);
|
if (todo) {
|
||||||
if (todo.status === "pending" && due_date > new Date()) {
|
const due_date = new Date(todo.due_date);
|
||||||
await this.mongoModel.updateTodoStatus(todo);
|
if (todo.status === "pending" && due_date > this.currentDate.getDate()) {
|
||||||
await this.sendNotification(todo); // Send notification to user
|
await this.mongoModel.updateTodoStatus(todo);
|
||||||
|
await this.sendNotification(todo); // Send notification to user
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
console.error("todo Not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@ export class MongoDbModel {
|
||||||
{ _id: new ObjectId(todo._id) },
|
{ _id: new ObjectId(todo._id) },
|
||||||
{ $set: { status: "completed" } }
|
{ $set: { status: "completed" } }
|
||||||
);
|
);
|
||||||
console.log(`Updated status of Todo ${todo._id} to completed`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error updating Todo status:", error);
|
console.error("Error updating Todo status:", error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|
|
@ -57,7 +57,6 @@ export class RabbitMQ {
|
||||||
Buffer.from(message),
|
Buffer.from(message),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
console.log("Message sent to the queue");
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error sending message to RabbitMQ:", error);
|
console.error("Error sending message to RabbitMQ:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
@ -68,8 +67,6 @@ export class RabbitMQ {
|
||||||
this.channel.assertQueue(this.queueName);
|
this.channel.assertQueue(this.queueName);
|
||||||
this.channel.consume(this.queueName, (message: ConsumeMessage | null) => {
|
this.channel.consume(this.queueName, (message: ConsumeMessage | null) => {
|
||||||
if (message) {
|
if (message) {
|
||||||
console.log("Message received from the queue");
|
|
||||||
console.log("Message content: ", message.content.toString());
|
|
||||||
const todo: ITodo = JSON.parse(message.content.toString()).payload;
|
const todo: ITodo = JSON.parse(message.content.toString()).payload;
|
||||||
this.channel.ack(message);
|
this.channel.ack(message);
|
||||||
callback(todo);
|
callback(todo);
|
||||||
|
|
20
notification-service/src/services/DateService.ts
Normal file
20
notification-service/src/services/DateService.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
|
||||||
|
export class DateService {
|
||||||
|
private static instance: DateService;
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): DateService {
|
||||||
|
if (!DateService.instance) {
|
||||||
|
DateService.instance = new DateService();
|
||||||
|
}
|
||||||
|
return DateService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentDate(): Date {
|
||||||
|
const israelTime = moment().tz("Asia/Jerusalem");
|
||||||
|
const currentDate = israelTime.toDate();
|
||||||
|
return currentDate;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ Content-Type: application/json
|
||||||
}
|
}
|
||||||
|
|
||||||
### update request
|
### update request
|
||||||
PUT http://localhost:3000/todo/64a9e3987a2dba152b1e44b0
|
PUT http://localhost:3000/todo/64aa7b55f73a81f5e374663b
|
||||||
Content-Type: application/json
|
Content-Type: application/json
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node dist/main.js"
|
"dev": "nodemon dist/main.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { Request, Response, NextFunction } from "express";
|
||||||
import { ApiError } from "../utils/ApiError";
|
import { ApiError } from "../utils/ApiError";
|
||||||
import { DateTime } from "luxon";
|
import { DateTime } from "luxon";
|
||||||
import mongoose from "mongoose";
|
import mongoose from "mongoose";
|
||||||
|
import { DateService } from "../services/DateService";
|
||||||
|
|
||||||
|
const currentDate = DateService.getInstance().getCurrentDate();
|
||||||
|
|
||||||
const createTodoMiddleWare = async (
|
const createTodoMiddleWare = async (
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -27,7 +30,7 @@ const createTodoMiddleWare = async (
|
||||||
);
|
);
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
if (new Date(due_date) < new Date()) {
|
if (new Date(due_date) < currentDate) {
|
||||||
const error = new ApiError(
|
const error = new ApiError(
|
||||||
`due_date must be greater than current date`,
|
`due_date must be greater than current date`,
|
||||||
400,
|
400,
|
||||||
|
@ -48,7 +51,6 @@ const paramIdMiddleware = async (
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
) => {
|
) => {
|
||||||
// check if it's a valid mongo id
|
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
if (!mongoose.Types.ObjectId.isValid(id)) {
|
if (!mongoose.Types.ObjectId.isValid(id)) {
|
||||||
const error = new ApiError(
|
const error = new ApiError(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import amqp, { Options } from "amqplib";
|
import amqp, { Options } from "amqplib";
|
||||||
import { ITodo } from "../schemas/todoSchema";
|
import { ITodo } from "../schemas/todoSchema";
|
||||||
import { EnvService } from "../services/EnvService";
|
import { EnvService } from "../services/EnvService";
|
||||||
|
import { DateService } from "../services/DateService";
|
||||||
|
|
||||||
export class RabbitMQ {
|
export class RabbitMQ {
|
||||||
connection: amqp.Connection;
|
connection: amqp.Connection;
|
||||||
|
@ -8,8 +9,10 @@ export class RabbitMQ {
|
||||||
queue: string;
|
queue: string;
|
||||||
exchange: string;
|
exchange: string;
|
||||||
envService: EnvService;
|
envService: EnvService;
|
||||||
|
currentDate: DateService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.currentDate = DateService.getInstance();
|
||||||
this.envService = EnvService.getInstance();
|
this.envService = EnvService.getInstance();
|
||||||
this.queue = this.envService.getEnvVariable("RABBITMQ_QUEUE_NAME");
|
this.queue = this.envService.getEnvVariable("RABBITMQ_QUEUE_NAME");
|
||||||
this.exchange = "delayed_exchange";
|
this.exchange = "delayed_exchange";
|
||||||
|
@ -44,15 +47,12 @@ export class RabbitMQ {
|
||||||
await this.channel.bindQueue(this.queue, this.exchange, this.queue);
|
await this.channel.bindQueue(this.queue, this.exchange, this.queue);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error connecting to RabbitMQ:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(payload: ITodo) {
|
async create(payload: ITodo) {
|
||||||
const delayTimeForQueue = this.calculateDelayTimeForQueue(payload);
|
const delayTimeForQueue = this.calculateDelayTimeForQueue(payload);
|
||||||
console.log("The Queue will be delayed for: ", delayTimeForQueue, " ms");
|
|
||||||
|
|
||||||
const message = JSON.stringify({ payload });
|
const message = JSON.stringify({ payload });
|
||||||
const options: Options.Publish = {
|
const options: Options.Publish = {
|
||||||
headers: { "x-delay": delayTimeForQueue },
|
headers: { "x-delay": delayTimeForQueue },
|
||||||
|
@ -64,15 +64,13 @@ export class RabbitMQ {
|
||||||
Buffer.from(message),
|
Buffer.from(message),
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
console.log(`Queue name is: ${this.queue}`);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error sending message to RabbitMQ:", error);
|
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateDelayTimeForQueue(payload: ITodo) {
|
calculateDelayTimeForQueue(payload: ITodo) {
|
||||||
const delayTime = payload.due_date.getTime() - Date.now();
|
const delayTime = payload.due_date.getTime() - this.currentDate.getCurrentDate().getTime();
|
||||||
return delayTime;
|
return delayTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,12 +4,15 @@ import {
|
||||||
createTodoMiddleWare,
|
createTodoMiddleWare,
|
||||||
paramIdMiddleware,
|
paramIdMiddleware,
|
||||||
} from "../middleware/createTodoMiddleWare";
|
} from "../middleware/createTodoMiddleWare";
|
||||||
|
import { DateService } from "../services/DateService";
|
||||||
|
|
||||||
class TodoRouter {
|
class TodoRouter {
|
||||||
router: Router;
|
router: Router;
|
||||||
todoController: TodoController;
|
todoController: TodoController;
|
||||||
|
currentDate: DateService;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.currentDate = DateService.getInstance();
|
||||||
this.router = Router();
|
this.router = Router();
|
||||||
this.todoController = new TodoController();
|
this.todoController = new TodoController();
|
||||||
this.setRoutes();
|
this.setRoutes();
|
||||||
|
|
20
todo-service/src/services/DateService.ts
Normal file
20
todo-service/src/services/DateService.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import moment from "moment-timezone";
|
||||||
|
|
||||||
|
export class DateService {
|
||||||
|
private static instance: DateService;
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): DateService {
|
||||||
|
if (!DateService.instance) {
|
||||||
|
DateService.instance = new DateService();
|
||||||
|
}
|
||||||
|
return DateService.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentDate(): Date {
|
||||||
|
const israelTime = moment().tz("Asia/Jerusalem");
|
||||||
|
const currentDate = israelTime.toDate();
|
||||||
|
return currentDate;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue