prettier
This commit is contained in:
parent
a0334bf886
commit
3e91c159d7
18 changed files with 222 additions and 333 deletions
22
README.md
22
README.md
|
@ -10,56 +10,62 @@ This README.md file provides instructions for setting up and running various com
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
docker-compose up -d
|
docker-compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## SQS/Terraform
|
## SQS/Terraform
|
||||||
|
|
||||||
Before proceeding, ensure that you have Terraform installed and have configured AWS CLI with the keys of an authorized user.
|
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.
|
1. Navigate to the /infra/terraform directory.
|
||||||
|
|
||||||
2. Run the following command to initialize Terraform:
|
2. Run the following command to initialize Terraform:
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
terraform init
|
terraform init
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Run the following command to apply the Terraform configuration:
|
3. Run the following command to apply the Terraform configuration:
|
||||||
|
|
||||||
```terraform
|
```terraform
|
||||||
terraform apply
|
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.
|
||||||
|
|
||||||
Navigate to the /notification-service directory.
|
Navigate to the /notification-service directory.
|
||||||
|
|
||||||
Run the following command to install the dependencies:
|
Run the following command to install the dependencies:
|
||||||
|
|
||||||
```npm
|
```npm
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the following command to build and run (Dem mode)the project:
|
Run the following command to build and run (Dem mode)the project:
|
||||||
|
|
||||||
```npm
|
```npm
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## TodoService
|
## TodoService
|
||||||
|
|
||||||
Before running the TodoService, ensure that you have Node.js, TypeScript (tsc) and npm installed.
|
Before running the TodoService, ensure that you have Node.js, TypeScript (tsc) and npm installed.
|
||||||
|
|
||||||
Navigate to the /todo-service directory.
|
Navigate to the /todo-service directory.
|
||||||
|
|
||||||
Run the following command to install the dependencies:
|
Run the following command to install the dependencies:
|
||||||
|
|
||||||
```npm
|
```npm
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
Run the following command to build and run (Dem mode)the project:
|
Run the following command to build and run (Dem mode)the project:
|
||||||
|
|
||||||
```npm
|
```npm
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Requests
|
## Requests
|
||||||
|
|
||||||
Examples are located in request.http - can be run in VSCode with the REST Client extension.
|
Examples are located in request.http - can be run in VSCode with the REST Client extension.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
version: '3.8'
|
version: "3.8"
|
||||||
services:
|
services:
|
||||||
mongodb:
|
mongodb:
|
||||||
image: arm64v8/mongo:4.0
|
image: arm64v8/mongo:4.0
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
requirepass your_password
|
|
|
@ -9,4 +9,4 @@ resource "aws_sqs_queue" "queue" {
|
||||||
message_retention_seconds = 345600
|
message_retention_seconds = 345600
|
||||||
visibility_timeout_seconds = 30
|
visibility_timeout_seconds = 30
|
||||||
receive_wait_time_seconds = 0
|
receive_wait_time_seconds = 0
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,3 @@
|
||||||
## AWS-SDK ##
|
|
||||||
AWS_ACCESS_KEY_ID={AWS_ACCESS_KEY_ID}
|
|
||||||
AWS_SECRET_ACCESS_KEY={AWS_SECRET_ACCESS_KEY}
|
|
||||||
AWS_REGION={AWS_REGION}
|
|
||||||
AWS_SQS_URL=https://sqs.{AWS_REGION}.amazonaws.com/{ACCOUNT_ID}/
|
|
||||||
AWS_SQS_QUEUE_NAME={NAME_OF_QUEUE}
|
|
||||||
|
|
||||||
## MONGO USER ##
|
## MONGO USER ##
|
||||||
DATABASE_URL=mongodb://{USER}:{PASSWORD}@localhost:27017/{DB_NAME}
|
DATABASE_URL=mongodb://{USER}:{PASSWORD}@localhost:27017/{DB_NAME}
|
||||||
MONGO_DB_NAME={USER_DB_NAME}
|
MONGO_DB_NAME={USER_DB_NAME}
|
||||||
|
|
|
@ -1,122 +0,0 @@
|
||||||
// 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);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from "mongodb";
|
||||||
|
|
||||||
export interface ITodo {
|
export interface ITodo {
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
|
@ -8,4 +8,4 @@ export interface ITodo {
|
||||||
createAt: Date;
|
createAt: Date;
|
||||||
updateAt: Date;
|
updateAt: Date;
|
||||||
status: string;
|
status: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { MongoClient, ObjectId } from 'mongodb';
|
import { MongoClient, ObjectId } from "mongodb";
|
||||||
import { ITodo } from '../interfaces/ITodo';
|
import { ITodo } from "../interfaces/ITodo";
|
||||||
|
|
||||||
const env = require('dotenv').config().parsed;
|
const env = require("dotenv").config().parsed;
|
||||||
|
|
||||||
export class MongoDbModel {
|
export class MongoDbModel {
|
||||||
client: MongoClient;
|
client: MongoClient;
|
||||||
|
@ -14,10 +14,10 @@ export class MongoDbModel {
|
||||||
try {
|
try {
|
||||||
await this.client.connect();
|
await this.client.connect();
|
||||||
const db = this.client.db(env.MONGO_DB_NAME);
|
const db = this.client.db(env.MONGO_DB_NAME);
|
||||||
const todosCollection = db.collection('todos');
|
const todosCollection = db.collection("todos");
|
||||||
const result = await todosCollection.updateOne(
|
const result = await todosCollection.updateOne(
|
||||||
{ _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`);
|
console.log(`Updated status of Todo ${todo._id} to completed`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -25,5 +25,5 @@ export class MongoDbModel {
|
||||||
} finally {
|
} finally {
|
||||||
await this.client.close();
|
await this.client.close();
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import amqp, { ConsumeMessage } from 'amqplib';
|
import amqp, { ConsumeMessage } from "amqplib";
|
||||||
import { ITodo } from '../interfaces/ITodo';
|
import { ITodo } from "../interfaces/ITodo";
|
||||||
import { MongoDbModel } from '../mongodb/MongoDb';
|
import { MongoDbModel } from "../mongodb/MongoDb";
|
||||||
|
|
||||||
const env = require('dotenv').config().parsed;
|
const env = require("dotenv").config().parsed;
|
||||||
|
|
||||||
export class RabbitMQ {
|
export class RabbitMQ {
|
||||||
channel: amqp.Channel;
|
channel: amqp.Channel;
|
||||||
|
@ -15,17 +15,19 @@ export class RabbitMQ {
|
||||||
|
|
||||||
this.mongoClient = new MongoDbModel();
|
this.mongoClient = new MongoDbModel();
|
||||||
|
|
||||||
this.connect().then(() => {
|
this.connect()
|
||||||
console.log('RabbitMQ connected');
|
.then(() => {
|
||||||
}).catch((error) => {
|
console.log("RabbitMQ connected");
|
||||||
console.error('Error connecting to RabbitMQ:', error);
|
})
|
||||||
});
|
.catch((error) => {
|
||||||
|
console.error("Error connecting to RabbitMQ:", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
try {
|
try {
|
||||||
const connection = await amqp.connect({
|
const connection = await amqp.connect({
|
||||||
protocol: 'amqp',
|
protocol: "amqp",
|
||||||
hostname: env.RABBITMQ_HOST,
|
hostname: env.RABBITMQ_HOST,
|
||||||
port: parseInt(env.RABBITMQ_PORT),
|
port: parseInt(env.RABBITMQ_PORT),
|
||||||
username: env.RABBITMQ_USERNAME,
|
username: env.RABBITMQ_USERNAME,
|
||||||
|
@ -34,22 +36,29 @@ export class RabbitMQ {
|
||||||
|
|
||||||
this.channel = await connection.createChannel();
|
this.channel = await connection.createChannel();
|
||||||
await this.channel.assertQueue(this.queueName);
|
await this.channel.assertQueue(this.queueName);
|
||||||
console.log('Channel and queue asserted successfully #####');
|
console.log("Channel and queue asserted successfully #####");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error connecting to RabbitMQ:', error);
|
console.error("Error connecting to RabbitMQ:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(payload: ITodo, delayTimeForQueue: number) {
|
async create(payload: ITodo, delayTimeForQueue: number) {
|
||||||
const message = JSON.stringify({ payload, delayTimeForQueue });
|
const message = JSON.stringify({ payload, delayTimeForQueue });
|
||||||
const options = { persistent: true, expiration: delayTimeForQueue.toString() };
|
const options = {
|
||||||
|
persistent: true,
|
||||||
|
expiration: delayTimeForQueue.toString(),
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.channel.sendToQueue(this.queueName, Buffer.from(message), options);
|
await this.channel.sendToQueue(
|
||||||
console.log('Message sent to the queue');
|
this.queueName,
|
||||||
|
Buffer.from(message),
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,13 +70,12 @@ export class RabbitMQ {
|
||||||
if (message) {
|
if (message) {
|
||||||
const todo = JSON.parse(message.content.toString());
|
const todo = JSON.parse(message.content.toString());
|
||||||
this.channel.ack(message);
|
this.channel.ack(message);
|
||||||
console.log('Received notification:', todo);
|
console.log("Received notification:", todo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// try {
|
||||||
// try {
|
|
||||||
// console.log('Consumer started, waiting for messages...');
|
// console.log('Consumer started, waiting for messages...');
|
||||||
// this.channel.consume(this.queueName, async (message) => {
|
// this.channel.consume(this.queueName, async (message) => {
|
||||||
// if (message) {
|
// if (message) {
|
||||||
|
@ -94,4 +102,4 @@ export class RabbitMQ {
|
||||||
// } catch (error) {
|
// } catch (error) {
|
||||||
// console.error('Error consuming messages from RabbitMQ:', error);
|
// console.error('Error consuming messages from RabbitMQ:', error);
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { ApiError } from '../utils/ApiError';
|
import { ApiError } from "../utils/ApiError";
|
||||||
import { ITodo } from '../schemas/todoSchema';
|
import { ITodo } from "../schemas/todoSchema";
|
||||||
import { TodoModel } from '../models/todoModel';
|
import { TodoModel } from "../models/todoModel";
|
||||||
// import { Sqs } from '../aws/Sqs';
|
// import { Sqs } from '../aws/Sqs';
|
||||||
import { RabbitMQ } from '../rabbitmq/RabbitMQ';
|
import { RabbitMQ } from "../rabbitmq/RabbitMQ";
|
||||||
|
|
||||||
const env = require('dotenv').config().parsed;
|
const env = require("dotenv").config().parsed;
|
||||||
|
|
||||||
export class TodoController {
|
export class TodoController {
|
||||||
private todoModel: TodoModel;
|
private todoModel: TodoModel;
|
||||||
|
@ -23,7 +23,7 @@ export class TodoController {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json(todos);
|
return res.json(todos);
|
||||||
}
|
};
|
||||||
|
|
||||||
public getOne = async (req: Request, res: Response, next: NextFunction) => {
|
public getOne = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const todo: ITodo | ApiError = await this.todoModel.findOne(req.params.id);
|
const todo: ITodo | ApiError = await this.todoModel.findOne(req.params.id);
|
||||||
|
@ -31,9 +31,13 @@ export class TodoController {
|
||||||
return next(todo);
|
return next(todo);
|
||||||
}
|
}
|
||||||
return res.json(todo);
|
return res.json(todo);
|
||||||
}
|
};
|
||||||
|
|
||||||
public createOne = async (req: Request, res: Response, next: NextFunction) => {
|
public createOne = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const todo: ITodo | ApiError = await this.todoModel.create(req.body);
|
const todo: ITodo | ApiError = await this.todoModel.create(req.body);
|
||||||
if (todo instanceof ApiError) {
|
if (todo instanceof ApiError) {
|
||||||
|
@ -43,31 +47,50 @@ export class TodoController {
|
||||||
|
|
||||||
return res.json(todo);
|
return res.json(todo);
|
||||||
} catch {
|
} catch {
|
||||||
const err = new ApiError('Internal server error', 500, 'Internal Server Error');
|
const err = new ApiError(
|
||||||
next(err)
|
"Internal server error",
|
||||||
|
500,
|
||||||
|
"Internal Server Error"
|
||||||
|
);
|
||||||
|
next(err);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
public updateOne = async (req: Request, res: Response, next: NextFunction) => {
|
public updateOne = async (
|
||||||
const todo: ITodo | ApiError = await this.todoModel.update(req.body, req.params.id);
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
|
const todo: ITodo | ApiError = await this.todoModel.update(
|
||||||
|
req.body,
|
||||||
|
req.params.id
|
||||||
|
);
|
||||||
if (todo instanceof ApiError) {
|
if (todo instanceof ApiError) {
|
||||||
return next(todo);
|
return next(todo);
|
||||||
}
|
}
|
||||||
return res.json(todo);
|
return res.json(todo);
|
||||||
}
|
};
|
||||||
|
|
||||||
public deleteOne = async (req: Request, res: Response, next: NextFunction) => {
|
public deleteOne = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const todo: boolean | ApiError = await this.todoModel.remove(id);
|
const todo: boolean | ApiError = await this.todoModel.remove(id);
|
||||||
if (!todo) {
|
if (!todo) {
|
||||||
const error = new ApiError('Todo not found', 404, 'Not Found');
|
const error = new ApiError("Todo not found", 404, "Not Found");
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
return res.json(todo);
|
return res.json(todo);
|
||||||
}
|
};
|
||||||
|
|
||||||
public removeAll = async (req: Request, res: Response, next: NextFunction) => {
|
public removeAll = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
const todos: boolean | ApiError = await this.todoModel.removeAll();
|
const todos: boolean | ApiError = await this.todoModel.removeAll();
|
||||||
return res.json(todos);
|
return res.json(todos);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import express from 'express';
|
import express from "express";
|
||||||
import mongoose from 'mongoose';
|
import mongoose from "mongoose";
|
||||||
import todoRouter from './routes/todoRouter';
|
import todoRouter from "./routes/todoRouter";
|
||||||
|
|
||||||
import { ApiError } from './utils/ApiError';
|
import { ApiError } from "./utils/ApiError";
|
||||||
|
|
||||||
|
const env = require("dotenv").config().parsed;
|
||||||
const env = require('dotenv').config().parsed;
|
|
||||||
|
|
||||||
class TodoApp {
|
class TodoApp {
|
||||||
app: express.Application;
|
app: express.Application;
|
||||||
|
@ -22,10 +21,10 @@ class TodoApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setRoutes() {
|
private setRoutes() {
|
||||||
this.app.use('/todo', todoRouter);
|
this.app.use("/todo", todoRouter);
|
||||||
this.app.all('*', (req, res, next) => {
|
this.app.all("*", (req, res, next) => {
|
||||||
const error = new ApiError('Are you lost?', 404, 'Not Found');
|
const error = new ApiError("Are you lost?", 404, "Not Found");
|
||||||
next(error)
|
next(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,12 +43,12 @@ class TodoApp {
|
||||||
mongoose.connect(env.DATABASE_URL);
|
mongoose.connect(env.DATABASE_URL);
|
||||||
const db = mongoose.connection;
|
const db = mongoose.connection;
|
||||||
// Check for DB connection
|
// Check for DB connection
|
||||||
db.on('error', () => {
|
db.on("error", () => {
|
||||||
console.error.bind(console, 'MongoDB connection error:')
|
console.error.bind(console, "MongoDB connection error:");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
db.once('open', () => {
|
db.once("open", () => {
|
||||||
console.log('Connected to MongoDB');
|
console.log("Connected to MongoDB");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,52 @@
|
||||||
import { Request, Response, NextFunction } from 'express';
|
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 moment from 'moment-timezone';
|
import moment from "moment-timezone";
|
||||||
|
|
||||||
const createTodoMiddleWare = async (req: Request, res: Response, next: NextFunction) => {
|
const createTodoMiddleWare = async (
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
) => {
|
||||||
const { title, description, due_date } = req.body;
|
const { title, description, due_date } = req.body;
|
||||||
if (!title || !due_date) {
|
if (!title || !due_date) {
|
||||||
const error = new ApiError(`${!title ? 'title' : 'due_date'} is required`, 400, 'Bad Request');
|
const error = new ApiError(
|
||||||
|
`${!title ? "title" : "due_date"} is required`,
|
||||||
|
400,
|
||||||
|
"Bad Request"
|
||||||
|
);
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if(new Date(due_date) < new Date()) {
|
if (new Date(due_date) < new Date()) {
|
||||||
const error = new ApiError(`due_date must be greater than current date`, 400, 'Bad Request');
|
const error = new ApiError(
|
||||||
|
`due_date must be greater than current date`,
|
||||||
|
400,
|
||||||
|
"Bad Request"
|
||||||
|
);
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
const error = new ApiError(`due_date must be ISO `, 400, 'Bad Request');
|
const error = new ApiError(`due_date must be ISO `, 400, "Bad Request");
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!description) {
|
if (!description) {
|
||||||
req.body.description = '';
|
req.body.description = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
//check if date is valid, this is valid: 2023-07-08T14:00:00.000Z
|
//check if date is valid, this is valid: 2023-07-08T14:00:00.000Z
|
||||||
const date = DateTime.fromISO(due_date);
|
const date = DateTime.fromISO(due_date);
|
||||||
if (!date.isValid) {
|
if (!date.isValid) {
|
||||||
const error = new ApiError(`due_date must be a valid date Format`, 400, 'Bad Request');
|
const error = new ApiError(
|
||||||
|
`due_date must be a valid date Format`,
|
||||||
|
400,
|
||||||
|
"Bad Request"
|
||||||
|
);
|
||||||
return next(error);
|
return next(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
next();
|
next();
|
||||||
}
|
};
|
||||||
|
|
||||||
export {
|
export { createTodoMiddleWare };
|
||||||
createTodoMiddleWare
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,66 +1,77 @@
|
||||||
import { Todo, ITodo } from '../schemas/todoSchema';
|
import { Todo, ITodo } from "../schemas/todoSchema";
|
||||||
import { ApiError } from '../utils/ApiError';
|
import { ApiError } from "../utils/ApiError";
|
||||||
|
|
||||||
interface Todo {
|
interface Todo {
|
||||||
title: string,
|
title: string;
|
||||||
description: string,
|
description: string;
|
||||||
due_date: Date
|
due_date: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TodoModel {
|
export class TodoModel {
|
||||||
|
|
||||||
public findAll = async (): Promise<ITodo[]> => {
|
public findAll = async (): Promise<ITodo[]> => {
|
||||||
const todos = await Todo.find();
|
const todos = await Todo.find();
|
||||||
return todos;
|
return todos;
|
||||||
}
|
};
|
||||||
|
|
||||||
public findOne = async (_id: string): Promise<ITodo | ApiError> => {
|
public findOne = async (_id: string): Promise<ITodo | ApiError> => {
|
||||||
try {
|
try {
|
||||||
const todo = await Todo.findById(_id);
|
const todo = await Todo.findById(_id);
|
||||||
if (!todo) {
|
if (!todo) {
|
||||||
const error = new ApiError('Todo not found', 404, 'Not Found');
|
const error = new ApiError("Todo not found", 404, "Not Found");
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
return todo;
|
return todo;
|
||||||
} catch {
|
} catch {
|
||||||
const error = new ApiError('Internal server error', 500, 'Internal Server Error');
|
const error = new ApiError(
|
||||||
|
"Internal server error",
|
||||||
|
500,
|
||||||
|
"Internal Server Error"
|
||||||
|
);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
public create = async (params: Todo): Promise<ITodo | ApiError> => {
|
public create = async (params: Todo): Promise<ITodo | ApiError> => {
|
||||||
let todo = new Todo(params);
|
let todo = new Todo(params);
|
||||||
todo = await todo.save();
|
todo = await todo.save();
|
||||||
if (!todo) {
|
if (!todo) {
|
||||||
const error = new ApiError('Internal server error', 500, 'Internal Server Error');
|
const error = new ApiError(
|
||||||
|
"Internal server error",
|
||||||
|
500,
|
||||||
|
"Internal Server Error"
|
||||||
|
);
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
return todo;
|
return todo;
|
||||||
}
|
};
|
||||||
|
|
||||||
public update = async (params: Todo, _id: string): Promise<ITodo | ApiError> => {
|
public update = async (
|
||||||
|
params: Todo,
|
||||||
|
_id: string
|
||||||
|
): Promise<ITodo | ApiError> => {
|
||||||
const todo = await Todo.findOne({ _id });
|
const todo = await Todo.findOne({ _id });
|
||||||
if (!todo) {
|
if (!todo) {
|
||||||
const error = new ApiError('Todo not found', 404, 'Not Found');
|
const error = new ApiError("Todo not found", 404, "Not Found");
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
todo.title = params.title;
|
todo.title = params.title;
|
||||||
todo.description = params.description;
|
todo.description = params.description;
|
||||||
todo.due_date = params.due_date;
|
todo.due_date = params.due_date;
|
||||||
|
|
||||||
await todo.save();
|
await todo.save();
|
||||||
return todo;
|
return todo;
|
||||||
}
|
};
|
||||||
|
|
||||||
public remove = async (_id: string): Promise<boolean | ApiError> => {
|
public remove = async (_id: string): Promise<boolean | ApiError> => {
|
||||||
const result = await Todo.deleteOne({ _id });
|
const result = await Todo.deleteOne({ _id });
|
||||||
return result.deletedCount > 0 ? true : new ApiError('Todo not found', 404, 'Not Found');
|
return result.deletedCount > 0
|
||||||
}
|
? true
|
||||||
|
: new ApiError("Todo not found", 404, "Not Found");
|
||||||
|
};
|
||||||
|
|
||||||
public removeAll = async (): Promise<boolean> => {
|
public removeAll = async (): Promise<boolean> => {
|
||||||
await Todo.deleteMany({});
|
await Todo.deleteMany({});
|
||||||
return true;
|
return true;
|
||||||
}
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import amqp, { Options, ConsumeMessage, Channel } from 'amqplib';
|
import amqp, { Options, ConsumeMessage, Channel } from "amqplib";
|
||||||
import { ITodo } from '../schemas/todoSchema';
|
import { ITodo } from "../schemas/todoSchema";
|
||||||
|
|
||||||
const env = require('dotenv').config().parsed;
|
const env = require("dotenv").config().parsed;
|
||||||
|
|
||||||
export class RabbitMQ {
|
export class RabbitMQ {
|
||||||
connection: amqp.Connection;
|
connection: amqp.Connection;
|
||||||
|
@ -9,22 +9,23 @@ export class RabbitMQ {
|
||||||
queue: string;
|
queue: string;
|
||||||
exchange: string;
|
exchange: string;
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.queue = env.RABBITMQ_QUEUE_NAME;
|
this.queue = env.RABBITMQ_QUEUE_NAME;
|
||||||
this.exchange = 'delayed_exchange';
|
this.exchange = "delayed_exchange";
|
||||||
|
|
||||||
this.connect().then(() => {
|
this.connect()
|
||||||
console.log('RabbitMQ connected');
|
.then(() => {
|
||||||
}).catch((error) => {
|
console.log("RabbitMQ connected");
|
||||||
console.error('Error connecting to RabbitMQ:', error);
|
})
|
||||||
});
|
.catch((error) => {
|
||||||
|
console.error("Error connecting to RabbitMQ:", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async connect() {
|
async connect() {
|
||||||
try {
|
try {
|
||||||
this.connection = await amqp.connect({
|
this.connection = await amqp.connect({
|
||||||
protocol: 'amqp',
|
protocol: "amqp",
|
||||||
hostname: env.RABBITMQ_HOST,
|
hostname: env.RABBITMQ_HOST,
|
||||||
port: parseInt(env.RABBITMQ_PORT),
|
port: parseInt(env.RABBITMQ_PORT),
|
||||||
username: env.RABBITMQ_USERNAME,
|
username: env.RABBITMQ_USERNAME,
|
||||||
|
@ -33,38 +34,41 @@ export class RabbitMQ {
|
||||||
|
|
||||||
this.channel = await this.connection.createChannel();
|
this.channel = await this.connection.createChannel();
|
||||||
await this.channel.assertQueue(this.queue);
|
await this.channel.assertQueue(this.queue);
|
||||||
await this.channel.assertExchange(this.exchange, 'x-delayed-message', {
|
await this.channel.assertExchange(this.exchange, "x-delayed-message", {
|
||||||
durable: true,
|
durable: true,
|
||||||
arguments: {
|
arguments: {
|
||||||
'x-delayed-type': 'direct'
|
"x-delayed-type": "direct",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
await this.channel.bindQueue(this.queue, this.exchange, this.queue);
|
await this.channel.bindQueue(this.queue, this.exchange, this.queue);
|
||||||
|
|
||||||
console.log('Channel and queue asserted successfully #####');
|
console.log("Channel and queue asserted successfully #####");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error connecting to RabbitMQ:', 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");
|
console.log("The Queue will be delayed for: ", delayTimeForQueue, " ms");
|
||||||
|
|
||||||
const message = JSON.stringify({ payload });
|
const message = JSON.stringify({ payload });
|
||||||
const options: Options.Publish = { headers: { 'x-delay': delayTimeForQueue } };
|
const options: Options.Publish = {
|
||||||
|
headers: { "x-delay": delayTimeForQueue },
|
||||||
|
};
|
||||||
try {
|
try {
|
||||||
await this.channel.publish(this.exchange, this.queue, Buffer.from(message),
|
await this.channel.publish(
|
||||||
options
|
this.exchange,
|
||||||
)
|
this.queue,
|
||||||
console.log(`Queue name is: ${this.queue}`)
|
Buffer.from(message),
|
||||||
|
options
|
||||||
|
);
|
||||||
|
console.log(`Queue name is: ${this.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
calculateDelayTimeForQueue(payload: ITodo) {
|
calculateDelayTimeForQueue(payload: ITodo) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Router } from 'express';
|
import { Router } from "express";
|
||||||
import { TodoController } from '../controllers/todoController';
|
import { TodoController } from "../controllers/todoController";
|
||||||
import { createTodoMiddleWare } from '../middleware/createTodoMiddleWare';
|
import { createTodoMiddleWare } from "../middleware/createTodoMiddleWare";
|
||||||
|
|
||||||
class TodoRouter {
|
class TodoRouter {
|
||||||
router: Router;
|
router: Router;
|
||||||
|
@ -13,19 +13,21 @@ class TodoRouter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private setRoutes() {
|
private setRoutes() {
|
||||||
this.router.get('/', this.todoController.getAll);
|
this.router.get("/", this.todoController.getAll);
|
||||||
this.router.get('/:id', this.todoController.getOne);
|
this.router.get("/:id", this.todoController.getOne);
|
||||||
this.router.post('/', createTodoMiddleWare, this.todoController.createOne);
|
this.router.post("/", createTodoMiddleWare, this.todoController.createOne);
|
||||||
this.router.put('/:id', createTodoMiddleWare, this.todoController.updateOne);
|
this.router.put(
|
||||||
this.router.delete('/:id', this.todoController.deleteOne);
|
"/:id",
|
||||||
this.router.delete('/', this.todoController.removeAll)
|
createTodoMiddleWare,
|
||||||
|
this.todoController.updateOne
|
||||||
|
);
|
||||||
|
this.router.delete("/:id", this.todoController.deleteOne);
|
||||||
|
this.router.delete("/", this.todoController.removeAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getRouter() {
|
public getRouter() {
|
||||||
return this.router;
|
return this.router;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new TodoRouter().getRouter();
|
export default new TodoRouter().getRouter();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import mongoose, { Schema, Document } from 'mongoose';
|
import mongoose, { Schema, Document } from "mongoose";
|
||||||
|
|
||||||
interface ITodo extends Document {
|
interface ITodo extends Document {
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -11,13 +11,13 @@ interface ITodo extends Document {
|
||||||
|
|
||||||
const TodoSchema: Schema<ITodo> = new Schema({
|
const TodoSchema: Schema<ITodo> = new Schema({
|
||||||
title: { type: String, required: true },
|
title: { type: String, required: true },
|
||||||
description: { type: String, default: '' },
|
description: { type: String, default: "" },
|
||||||
due_date: { type: Date, required: true },
|
due_date: { type: Date, required: true },
|
||||||
createAt: { type: Date, default: Date.now },
|
createAt: { type: Date, default: Date.now },
|
||||||
updateAt: { type: Date, default: Date.now },
|
updateAt: { type: Date, default: Date.now },
|
||||||
status: { type: String, enum: ['pending', 'completed'], default: 'pending' },
|
status: { type: String, enum: ["pending", "completed"], default: "pending" },
|
||||||
});
|
});
|
||||||
|
|
||||||
const Todo = mongoose.model<ITodo>('Todo', TodoSchema, 'todos');
|
const Todo = mongoose.model<ITodo>("Todo", TodoSchema, "todos");
|
||||||
|
|
||||||
export { Todo, ITodo };
|
export { Todo, ITodo };
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
class ApiError extends Error {
|
class ApiError extends Error {
|
||||||
constructor(message: string, private statusCode: number = 500,private status: string = 'Internal Server Error') {
|
constructor(
|
||||||
|
message: string,
|
||||||
|
private statusCode: number = 500,
|
||||||
|
private status: string = "Internal Server Error"
|
||||||
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { ApiError };
|
export { ApiError };
|
||||||
|
|
Loading…
Reference in a new issue