Compare commits

..

No commits in common. "e8265f5fe35cf87cb5b6f819e661b5033bfd83a7" and "429ddebf0217ce801994f07acf08dc79f5588a15" have entirely different histories.

15 changed files with 51 additions and 95 deletions

View file

@ -12,6 +12,24 @@ 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.

View file

@ -9,15 +9,11 @@ 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: todorabbit:latest image: kfda89/todorabbit:latest
restart: always restart: always
ports: ports:
- 5672:5672 - 5672:5672
@ -25,9 +21,6 @@ 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

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
}

View file

@ -11,7 +11,6 @@
"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": {
@ -134,25 +133,6 @@
"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",

View file

@ -12,7 +12,6 @@
"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": {

View file

@ -1,15 +1,12 @@
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();
@ -23,18 +20,14 @@ export class NotificationService {
} }
async newMessageValidator(message: ITodo) { async newMessageValidator(message: ITodo) {
try {
const todo = await this.mongoModel.getTodoById(message._id.toString()); const todo = await this.mongoModel.getTodoById(message._id.toString());
if (todo) { if (todo) {
const due_date = new Date(todo.due_date); const due_date = new Date(todo.due_date);
if (todo.status === "pending" && due_date > this.currentDate.getDate()) { if (todo.status === "pending" && due_date > new Date()) {
await this.mongoModel.updateTodoStatus(todo); await this.mongoModel.updateTodoStatus(todo);
await this.sendNotification(todo); // Send notification to user await this.sendNotification(todo); // Send notification to user
} }
} }
} catch {
console.error("todo Not found");
}
} }
private IsUserConnected() { private IsUserConnected() {

View file

@ -24,6 +24,7 @@ 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 {

View file

@ -57,6 +57,7 @@ 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;
@ -67,6 +68,8 @@ 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);

View file

@ -1,20 +0,0 @@
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;
}
}

View file

@ -13,7 +13,7 @@ Content-Type: application/json
} }
### update request ### update request
PUT http://localhost:3000/todo/64aa7b55f73a81f5e374663b PUT http://localhost:3000/todo/64a9e3987a2dba152b1e44b0
Content-Type: application/json Content-Type: application/json
{ {

View file

@ -4,7 +4,7 @@
"description": "", "description": "",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"dev": "nodemon dist/main.js" "dev": "node dist/main.js"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "",

View file

@ -2,9 +2,6 @@ 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,
@ -30,7 +27,7 @@ const createTodoMiddleWare = async (
); );
return next(error); return next(error);
} }
if (new Date(due_date) < currentDate) { if (new Date(due_date) < new Date()) {
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,
@ -51,6 +48,7 @@ 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(

View file

@ -1,7 +1,6 @@
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;
@ -9,10 +8,8 @@ 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";
@ -47,12 +44,15 @@ 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,13 +64,15 @@ 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() - this.currentDate.getCurrentDate().getTime(); const delayTime = payload.due_date.getTime() - Date.now();
return delayTime; return delayTime;
} }
} }

View file

@ -4,15 +4,12 @@ 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();

View file

@ -1,20 +0,0 @@
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;
}
}