├── .gitignore ├── README.md ├── chat-service ├── .dockerignore ├── .env.example ├── Dockerfile ├── package-lock.json ├── package.json ├── src │ ├── app.ts │ ├── config │ │ └── config.ts │ ├── controllers │ │ └── MessageController.ts │ ├── database │ │ ├── connection.ts │ │ ├── index.ts │ │ └── models │ │ │ └── MessageModel.ts │ ├── middleware │ │ └── index.ts │ ├── routes │ │ └── messageRoutes.ts │ ├── server.ts │ ├── services │ │ └── RabbitMQService.ts │ └── utils │ │ ├── apiError.ts │ │ ├── index.ts │ │ ├── messageHandler.ts │ │ └── userStatusStore.ts └── tsconfig.json ├── docker-compose.yml ├── gateway ├── index.ts ├── package-lock.json └── package.json ├── nginx ├── Dockerfile └── nginx.conf ├── notification-service ├── .dockerignore ├── .env.example ├── Dockerfile ├── package-lock.json ├── package.json ├── src │ ├── config │ │ └── config.ts │ ├── middleware │ │ └── index.ts │ ├── server.ts │ ├── services │ │ ├── EmailService.ts │ │ ├── FCMService.ts │ │ ├── RabbitMQService.ts │ │ └── index.ts │ └── utils │ │ ├── apiError.ts │ │ ├── index.ts │ │ └── userStatusStore.ts └── tsconfig.json └── user-service ├── .dockerignore ├── .env.example ├── Dockerfile ├── package-lock.json ├── package.json ├── src ├── config │ └── config.ts ├── controllers │ └── AuthController.ts ├── database │ ├── connection.ts │ ├── index.ts │ └── models │ │ └── UserModel.ts ├── middleware │ └── index.ts ├── routes │ └── authRoutes.ts ├── server.ts ├── services │ └── RabbitMQService.ts └── utils │ └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/.env 3 | **/build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chat Server - Microservices Tutorial 2 | 3 | This project serves as a comprehensive tutorial for building and deploying microservices using Node.js, RabbitMQ, Nginx, and Docker. 4 | 5 | ## Table of Contents 6 | 7 | - [Introduction](#introduction) 8 | - [Setup](#setup) 9 | - [Usage](#usage) 10 | - [Tutorial](#tutorial) 11 | 12 | ## Introduction 13 | 14 | This tutorial provides step-by-step guidance on developing and deploying a microservices architecture using Node.js, RabbitMQ, Nginx, and Docker. It covers the setup of individual microservices for user management, chat functionality, and notifications, as well as an API Gateway to manage communication between services. 15 | 16 | ## Setup 17 | 18 | To get started with the project, follow these steps: 19 | 20 | - Clone the repository to your local machine. 21 | - Create a `.env` file in each microservice folder based on the provided `.env.example` files and fill in the necessary configuration details. 22 | - Run `docker-compose up --build` from the project root directory to start the application in a containerized environment. 23 | 24 | ## Usage 25 | 26 | Once the application is up and running, you can interact with the microservices using Postman or any API tool of your choice. The application is accessible at http://localhost:85 and routes requests to the appropriate microservice based on the endpoint. 27 | 28 | ## Tutorial 29 | 30 | For the detailed tutorial on this project, visit [https://dev.to/davydocsurg/mastering-microservices-a-hands-on-tutorial-with-nodejs-rabbitmq-nginx-and-docker-m4f](https://dev.to/davydocsurg/mastering-microservices-a-hands-on-tutorial-with-nodejs-rabbitmq-nginx-and-docker-m4f). 31 | -------------------------------------------------------------------------------- /chat-service/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile 4 | .dockerignore 5 | .git 6 | .gitignore 7 | -------------------------------------------------------------------------------- /chat-service/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | PORT=8082 3 | MONGO_URI="{{YOUR_MONGODB_URI}}" 4 | JWT_SECRET="secret" 5 | MESSAGE_BROKER_URL="{{YOUR_MESSAGE_BROKER_URL}}" -------------------------------------------------------------------------------- /chat-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | EXPOSE 8082 14 | 15 | CMD [ "npm", "start"] 16 | -------------------------------------------------------------------------------- /chat-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chat-server", 3 | "version": "1.0.0", 4 | "description": "Chat Service - Chat Server", 5 | "main": "src/server.ts", 6 | "scripts": { 7 | "dev": "NODE_ENV=development nodemon src/server.ts", 8 | "build": "rm -rf build/ && tsc -p .", 9 | "start": "NODE_ENV=production nodemon build/server.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/davydocsurg/chat-server.git" 15 | }, 16 | "keywords": [ 17 | "typescript", 18 | "microservices", 19 | "javascript", 20 | "docker", 21 | "mongodb" 22 | ], 23 | "author": "davydocsurg", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/davydocsurg/chat-server/issues" 27 | }, 28 | "homepage": "https://github.com/davydocsurg/chat-server#readme", 29 | "dependencies": { 30 | "@types/express": "^4.17.21", 31 | "amqplib": "^0.10.3", 32 | "dotenv": "^16.4.1", 33 | "express": "^4.18.2", 34 | "jsonwebtoken": "^9.0.2", 35 | "mongoose": "^8.1.1", 36 | "nodemon": "^3.0.3", 37 | "socket.io": "^4.7.4", 38 | "ts-node": "^10.9.2", 39 | "typescript": "^5.3.3", 40 | "uuid": "^9.0.1" 41 | }, 42 | "devDependencies": { 43 | "@types/amqplib": "^0.10.4", 44 | "@types/body-parser": "^1.19.5", 45 | "@types/jsonwebtoken": "^9.0.5", 46 | "@types/uuid": "^9.0.8" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /chat-service/src/app.ts: -------------------------------------------------------------------------------- 1 | import express, { Express } from "express"; 2 | import userRouter from "./routes/messageRoutes"; 3 | import { errorConverter, errorHandler } from "./middleware"; 4 | 5 | const app: Express = express(); 6 | app.use(express.json()); 7 | app.use(express.urlencoded({ extended: true })); 8 | app.use(userRouter); 9 | app.use(errorConverter); 10 | app.use(errorHandler); 11 | 12 | export default app; 13 | -------------------------------------------------------------------------------- /chat-service/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | 3 | const configFile = `./.env`; 4 | config({ path: configFile }); 5 | 6 | const { MONGO_URI, PORT, JWT_SECRET, NODE_ENV, MESSAGE_BROKER_URL } = 7 | process.env; 8 | 9 | const queue = { notifications: "NOTIFICATIONS" }; 10 | 11 | export default { 12 | MONGO_URI, 13 | PORT, 14 | JWT_SECRET, 15 | env: NODE_ENV, 16 | msgBrokerURL: MESSAGE_BROKER_URL, 17 | queue, 18 | }; 19 | -------------------------------------------------------------------------------- /chat-service/src/controllers/MessageController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { AuthRequest } from "../middleware"; 3 | import { Message } from "../database"; 4 | import { ApiError, handleMessageReceived } from "../utils"; 5 | 6 | const send = async (req: AuthRequest, res: Response) => { 7 | try { 8 | const { receiverId, message } = req.body; 9 | const { _id, email, name } = req.user; 10 | 11 | validateReceiver(_id, receiverId); 12 | 13 | const newMessage = await Message.create({ 14 | senderId: _id, 15 | receiverId, 16 | message, 17 | }); 18 | 19 | await handleMessageReceived(name, email, receiverId, message); 20 | 21 | return res.json({ 22 | status: 200, 23 | message: "Message sent successfully!", 24 | data: newMessage, 25 | }); 26 | } catch (error: any) { 27 | return res.json({ 28 | status: 500, 29 | message: error.message, 30 | }); 31 | } 32 | }; 33 | 34 | const validateReceiver = (senderId: string, receiverId: string) => { 35 | if (!receiverId) { 36 | throw new ApiError(404, "Receiver ID is required."); 37 | } 38 | 39 | if (senderId == receiverId) { 40 | throw new ApiError(400, "Sender and receiver cannot be the same."); 41 | } 42 | }; 43 | 44 | const getConversation = async (req: AuthRequest, res: Response) => { 45 | try { 46 | const { receiverId } = req.params; 47 | const senderId = req.user._id; 48 | 49 | const messages = await Message.find({ 50 | $or: [ 51 | { senderId, receiverId }, 52 | { senderId: receiverId, receiverId: senderId }, 53 | ], 54 | }); 55 | 56 | return res.json({ 57 | status: 200, 58 | message: "Messages retrieved successfully!", 59 | data: messages, 60 | }); 61 | } catch (error: any) { 62 | return res.json({ 63 | status: 500, 64 | message: error.message, 65 | }); 66 | } 67 | }; 68 | 69 | export default { 70 | send, 71 | getConversation, 72 | }; 73 | -------------------------------------------------------------------------------- /chat-service/src/database/connection.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import config from "../config/config"; 3 | 4 | export const connectDB = async () => { 5 | try { 6 | console.info("Connecting to database..." + config.MONGO_URI); 7 | await mongoose.connect(config.MONGO_URI!); 8 | console.info("Database connected"); 9 | } catch (error) { 10 | console.error(error); 11 | process.exit(1); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /chat-service/src/database/index.ts: -------------------------------------------------------------------------------- 1 | import Message from "./models/MessageModel"; 2 | import { connectDB } from "./connection"; 3 | 4 | export { Message, connectDB }; 5 | -------------------------------------------------------------------------------- /chat-service/src/database/models/MessageModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema, Document } from "mongoose"; 2 | 3 | enum Status { 4 | NotDelivered = "NotDelivered", 5 | Delivered = "Delivered", 6 | Seen = "Seen", 7 | } 8 | 9 | export interface IMessage extends Document { 10 | senderId: string; 11 | receiverId: string; 12 | message: string; 13 | status: Status; 14 | createdAt: Date; 15 | updatedAt: Date; 16 | } 17 | 18 | const MessageSchema: Schema = new Schema( 19 | { 20 | senderId: { 21 | type: String, 22 | required: true, 23 | }, 24 | receiverId: { 25 | type: String, 26 | required: true, 27 | }, 28 | message: { 29 | type: String, 30 | required: true, 31 | }, 32 | status: { 33 | type: String, 34 | enum: Object.values(Status), 35 | default: Status.NotDelivered, 36 | }, 37 | }, 38 | { 39 | timestamps: true, 40 | } 41 | ); 42 | 43 | const Message = mongoose.model("Message", MessageSchema); 44 | export default Message; 45 | -------------------------------------------------------------------------------- /chat-service/src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction, ErrorRequestHandler } from "express"; 2 | import jwt from "jsonwebtoken"; 3 | import { ApiError } from "../utils"; 4 | import config from "../config/config"; 5 | 6 | interface TokenPayload { 7 | id: string; 8 | name: string; 9 | email: string; 10 | iat: number; 11 | exp: number; 12 | } 13 | 14 | interface IUser { 15 | _id: string; 16 | name: string; 17 | email: string; 18 | password: string; 19 | createdAt: Date; 20 | updatedAt: Date; 21 | } 22 | 23 | export interface AuthRequest extends Request { 24 | user: IUser; 25 | } 26 | 27 | const jwtSecret = config.JWT_SECRET as string; 28 | 29 | const authMiddleware = async ( 30 | req: AuthRequest, 31 | res: Response, 32 | next: NextFunction 33 | ) => { 34 | const authHeader = req.headers.authorization; 35 | if (!authHeader) { 36 | return next(new ApiError(401, "Missing authorization header")); 37 | } 38 | 39 | const [, token] = authHeader.split(" "); 40 | try { 41 | const decoded = jwt.verify(token, jwtSecret) as TokenPayload; 42 | 43 | req.user = { 44 | _id: decoded.id, 45 | email: decoded.email, 46 | createdAt: new Date(decoded.iat * 1000), 47 | updatedAt: new Date(decoded.exp * 1000), 48 | name: decoded.name, 49 | password: "", 50 | }; 51 | return next(); 52 | } catch (error) { 53 | console.error(error); 54 | return next(new ApiError(401, "Invalid token")); 55 | } 56 | }; 57 | 58 | const errorConverter: ErrorRequestHandler = (err, req, res, next) => { 59 | let error = err; 60 | if (!(error instanceof ApiError)) { 61 | const statusCode = 62 | error.statusCode || 63 | (error instanceof Error 64 | ? 400 // Bad Request 65 | : 500); // Internal Server Error 66 | const message = 67 | error.message || 68 | (statusCode === 400 ? "Bad Request" : "Internal Server Error"); 69 | error = new ApiError(statusCode, message, false, err.stack.toString()); 70 | } 71 | next(error); 72 | }; 73 | 74 | const errorHandler: ErrorRequestHandler = (err, req, res, next) => { 75 | let { statusCode, message } = err; 76 | if (process.env.NODE_ENV === "production" && !err.isOperational) { 77 | statusCode = 500; // Internal Server Error 78 | message = "Internal Server Error"; 79 | } 80 | 81 | res.locals.errorMessage = err.message; 82 | 83 | const response = { 84 | code: statusCode, 85 | message, 86 | ...(process.env.NODE_ENV === "development" && { stack: err.stack }), 87 | }; 88 | 89 | if (process.env.NODE_ENV === "development") { 90 | console.error(err); 91 | } 92 | 93 | res.status(statusCode).json(response); 94 | next(); 95 | }; 96 | 97 | export { authMiddleware, errorConverter, errorHandler }; 98 | -------------------------------------------------------------------------------- /chat-service/src/routes/messageRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import MessageController from "../controllers/MessageController"; 3 | import { authMiddleware } from "../middleware"; 4 | 5 | const messageRoutes = Router(); 6 | 7 | // @ts-ignore 8 | messageRoutes.post("/send", authMiddleware, MessageController.send); 9 | messageRoutes.get( 10 | "/get/:receiverId", 11 | // @ts-ignore 12 | authMiddleware, 13 | MessageController.getConversation 14 | ); 15 | 16 | export default messageRoutes; 17 | -------------------------------------------------------------------------------- /chat-service/src/server.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "http"; 2 | import { Socket, Server as SocketIOServer } from "socket.io"; 3 | import app from "./app"; 4 | import { Message, connectDB } from "./database"; 5 | import config from "./config/config"; 6 | 7 | let server: Server; 8 | connectDB(); 9 | 10 | server = app.listen(config.PORT, () => { 11 | console.log(`Server is running on port ${config.PORT}`); 12 | }); 13 | const io = new SocketIOServer(server); 14 | io.on("connection", (socket: Socket) => { 15 | console.log("Client connected"); 16 | socket.on("disconnect", () => { 17 | console.log("Client disconnected", socket.id); 18 | }); 19 | 20 | socket.on("sendMessage", (message) => { 21 | io.emit("receiveMessage", message); 22 | }); 23 | 24 | socket.on("sendMessage", async (data) => { 25 | const { senderId, receiverId, message } = data; 26 | const msg = new Message({ senderId, receiverId, message }); 27 | await msg.save(); 28 | 29 | io.to(receiverId).emit("receiveMessage", msg); // Assuming receiverId is socket ID of the receiver 30 | }); 31 | }); 32 | 33 | const exitHandler = () => { 34 | if (server) { 35 | server.close(() => { 36 | console.info("Server closed"); 37 | process.exit(1); 38 | }); 39 | } else { 40 | process.exit(1); 41 | } 42 | }; 43 | 44 | const unexpectedErrorHandler = (error: unknown) => { 45 | console.error(error); 46 | exitHandler(); 47 | }; 48 | 49 | process.on("uncaughtException", unexpectedErrorHandler); 50 | process.on("unhandledRejection", unexpectedErrorHandler); 51 | -------------------------------------------------------------------------------- /chat-service/src/services/RabbitMQService.ts: -------------------------------------------------------------------------------- 1 | import amqp, { Channel } from "amqplib"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | import config from "../config/config"; 4 | 5 | class RabbitMQService { 6 | private requestQueue = "USER_DETAILS_REQUEST"; 7 | private responseQueue = "USER_DETAILS_RESPONSE"; 8 | private correlationMap = new Map(); 9 | private channel!: Channel; 10 | 11 | constructor() { 12 | this.init(); 13 | } 14 | 15 | async init() { 16 | const connection = await amqp.connect(config.msgBrokerURL!); 17 | this.channel = await connection.createChannel(); 18 | await this.channel.assertQueue(this.requestQueue); 19 | await this.channel.assertQueue(this.responseQueue); 20 | 21 | this.channel.consume( 22 | this.responseQueue, 23 | (msg) => { 24 | if (msg) { 25 | const correlationId = msg.properties.correlationId; 26 | const user = JSON.parse(msg.content.toString()); 27 | 28 | const callback = this.correlationMap.get(correlationId); 29 | if (callback) { 30 | callback(user); 31 | this.correlationMap.delete(correlationId); 32 | } 33 | } 34 | }, 35 | { noAck: true } 36 | ); 37 | } 38 | 39 | async requestUserDetails(userId: string, callback: Function) { 40 | const correlationId = uuidv4(); 41 | this.correlationMap.set(correlationId, callback); 42 | this.channel.sendToQueue( 43 | this.requestQueue, 44 | Buffer.from(JSON.stringify({ userId })), 45 | { correlationId } 46 | ); 47 | } 48 | 49 | async notifyReceiver( 50 | receiverId: string, 51 | messageContent: string, 52 | senderEmail: string, 53 | senderName: string 54 | ) { 55 | await this.requestUserDetails(receiverId, async (user: any) => { 56 | const notificationPayload = { 57 | type: "MESSAGE_RECEIVED", 58 | userId: receiverId, 59 | userEmail: user.email, 60 | message: messageContent, 61 | from: senderEmail, 62 | fromName: senderName, 63 | }; 64 | 65 | try { 66 | await this.channel.assertQueue(config.queue.notifications); 67 | this.channel.sendToQueue( 68 | config.queue.notifications, 69 | Buffer.from(JSON.stringify(notificationPayload)) 70 | ); 71 | } catch (error) { 72 | console.error(error); 73 | } 74 | }); 75 | } 76 | } 77 | 78 | export const rabbitMQService = new RabbitMQService(); 79 | -------------------------------------------------------------------------------- /chat-service/src/utils/apiError.ts: -------------------------------------------------------------------------------- 1 | class ApiError extends Error { 2 | statusCode: number; 3 | isOperational: boolean; 4 | 5 | constructor( 6 | statusCode: number, 7 | message: string | undefined, 8 | isOperational = true, 9 | stack = "" 10 | ) { 11 | super(message); 12 | this.statusCode = statusCode; 13 | this.isOperational = isOperational; 14 | if (stack) { 15 | this.stack = stack; 16 | } else { 17 | Error.captureStackTrace(this, this.constructor); 18 | } 19 | } 20 | } 21 | 22 | export { ApiError }; 23 | -------------------------------------------------------------------------------- /chat-service/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { ApiError } from "./apiError"; 2 | import { UserStatusStore } from "./userStatusStore"; 3 | import { handleMessageReceived } from "./messageHandler"; 4 | 5 | export { ApiError, UserStatusStore, handleMessageReceived }; 6 | -------------------------------------------------------------------------------- /chat-service/src/utils/messageHandler.ts: -------------------------------------------------------------------------------- 1 | import { UserStatusStore } from "./userStatusStore"; 2 | import { rabbitMQService } from "../services/RabbitMQService"; 3 | 4 | const userStatusStore = UserStatusStore.getInstance(); 5 | 6 | export const handleMessageReceived = async ( 7 | senderName: string, 8 | senderEmail: string, 9 | receiverId: string, 10 | messageContent: string 11 | ) => { 12 | const receiverIsOffline = !userStatusStore.isUserOnline(receiverId); 13 | 14 | if (receiverIsOffline) { 15 | await rabbitMQService.notifyReceiver( 16 | receiverId, 17 | messageContent, 18 | senderEmail, 19 | senderName 20 | ); 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /chat-service/src/utils/userStatusStore.ts: -------------------------------------------------------------------------------- 1 | export class UserStatusStore { 2 | private static instance: UserStatusStore; 3 | private userStatuses: Record; 4 | 5 | private constructor() { 6 | this.userStatuses = {}; 7 | } 8 | 9 | public static getInstance(): UserStatusStore { 10 | if (!UserStatusStore.instance) { 11 | UserStatusStore.instance = new UserStatusStore(); 12 | } 13 | return UserStatusStore.instance; 14 | } 15 | 16 | setUserOnline(userId: string) { 17 | this.userStatuses[userId] = true; 18 | } 19 | 20 | setUserOffline(userId: string) { 21 | this.userStatuses[userId] = false; 22 | } 23 | 24 | isUserOnline(userId: string): boolean { 25 | return !!this.userStatuses[userId]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chat-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./build", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | }, 10 | "include": [ 11 | "src/**/*" 12 | ], 13 | "exclude": [ 14 | "node_modules", 15 | "tests" 16 | ] 17 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | mongodb: 4 | image: mongo:latest 5 | restart: unless-stopped 6 | ports: 7 | - "27017:27017" 8 | volumes: 9 | - mongo-data:/data/db 10 | 11 | user: 12 | build: 13 | context: ./user-service 14 | dockerfile: Dockerfile 15 | ports: 16 | - "8081:8081" 17 | restart: always 18 | depends_on: 19 | - "mongodb" 20 | environment: 21 | - NODE_ENV=production 22 | 23 | chat: 24 | build: 25 | context: ./chat-service 26 | dockerfile: Dockerfile 27 | ports: 28 | - "8082:8082" 29 | depends_on: 30 | - "mongodb" 31 | environment: 32 | - NODE_ENV=production 33 | 34 | notification: 35 | build: 36 | context: ./notification-service 37 | dockerfile: Dockerfile 38 | ports: 39 | - "8083:8083" 40 | depends_on: 41 | - "mongodb" 42 | environment: 43 | - NODE_ENV=production 44 | 45 | nginx: 46 | build: 47 | context: ./nginx 48 | dockerfile: Dockerfile 49 | ports: 50 | - "85:85" 51 | depends_on: 52 | - user 53 | - chat 54 | - notification 55 | 56 | volumes: 57 | mongo-data: -------------------------------------------------------------------------------- /gateway/index.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import proxy from "express-http-proxy"; 3 | 4 | const app = express(); 5 | app.use(express.json()); 6 | app.use(express.urlencoded({ extended: true })); 7 | 8 | const auth = proxy("http://localhost:8081"); 9 | const messages = proxy("http://localhost:8082"); 10 | const notifications = proxy("http://localhost:8083"); 11 | 12 | app.use("/api/auth", auth); 13 | app.use("/api/messages", messages); 14 | app.use("/api/notifications", notifications); 15 | 16 | const server = app.listen(8080, () => { 17 | console.log("Gateway is Listening to Port 8080"); 18 | }); 19 | 20 | const exitHandler = () => { 21 | if (server) { 22 | server.close(() => { 23 | console.info("Server closed"); 24 | process.exit(1); 25 | }); 26 | } else { 27 | process.exit(1); 28 | } 29 | }; 30 | 31 | const unexpectedErrorHandler = (error: unknown) => { 32 | console.error(error); 33 | exitHandler(); 34 | }; 35 | 36 | process.on("uncaughtException", unexpectedErrorHandler); 37 | process.on("unhandledRejection", unexpectedErrorHandler); 38 | -------------------------------------------------------------------------------- /gateway/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gateway", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "gateway", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@types/express": "^4.17.21", 13 | "@types/express-http-proxy": "^1.6.6", 14 | "express": "^4.18.2", 15 | "express-http-proxy": "^2.0.0", 16 | "nodemon": "^3.0.3", 17 | "ts-node": "^10.9.2" 18 | } 19 | }, 20 | "node_modules/@cspotcode/source-map-support": { 21 | "version": "0.8.1", 22 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 23 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 24 | "dependencies": { 25 | "@jridgewell/trace-mapping": "0.3.9" 26 | }, 27 | "engines": { 28 | "node": ">=12" 29 | } 30 | }, 31 | "node_modules/@jridgewell/resolve-uri": { 32 | "version": "3.1.1", 33 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 34 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 35 | "engines": { 36 | "node": ">=6.0.0" 37 | } 38 | }, 39 | "node_modules/@jridgewell/sourcemap-codec": { 40 | "version": "1.4.15", 41 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 42 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 43 | }, 44 | "node_modules/@jridgewell/trace-mapping": { 45 | "version": "0.3.9", 46 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 47 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 48 | "dependencies": { 49 | "@jridgewell/resolve-uri": "^3.0.3", 50 | "@jridgewell/sourcemap-codec": "^1.4.10" 51 | } 52 | }, 53 | "node_modules/@tsconfig/node10": { 54 | "version": "1.0.9", 55 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 56 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" 57 | }, 58 | "node_modules/@tsconfig/node12": { 59 | "version": "1.0.11", 60 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 61 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" 62 | }, 63 | "node_modules/@tsconfig/node14": { 64 | "version": "1.0.3", 65 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 66 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" 67 | }, 68 | "node_modules/@tsconfig/node16": { 69 | "version": "1.0.4", 70 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 71 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" 72 | }, 73 | "node_modules/@types/body-parser": { 74 | "version": "1.19.5", 75 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 76 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 77 | "dependencies": { 78 | "@types/connect": "*", 79 | "@types/node": "*" 80 | } 81 | }, 82 | "node_modules/@types/connect": { 83 | "version": "3.4.38", 84 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 85 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 86 | "dependencies": { 87 | "@types/node": "*" 88 | } 89 | }, 90 | "node_modules/@types/express": { 91 | "version": "4.17.21", 92 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", 93 | "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", 94 | "dependencies": { 95 | "@types/body-parser": "*", 96 | "@types/express-serve-static-core": "^4.17.33", 97 | "@types/qs": "*", 98 | "@types/serve-static": "*" 99 | } 100 | }, 101 | "node_modules/@types/express-http-proxy": { 102 | "version": "1.6.6", 103 | "resolved": "https://registry.npmjs.org/@types/express-http-proxy/-/express-http-proxy-1.6.6.tgz", 104 | "integrity": "sha512-J8ZqHG76rq1UB716IZ3RCmUhg406pbWxsM3oFCFccl5xlWUPzoR4if6Og/cE4juK8emH0H9quZa5ltn6ZdmQJg==", 105 | "dependencies": { 106 | "@types/express": "*" 107 | } 108 | }, 109 | "node_modules/@types/express-serve-static-core": { 110 | "version": "4.17.42", 111 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.42.tgz", 112 | "integrity": "sha512-ckM3jm2bf/MfB3+spLPWYPUH573plBFwpOhqQ2WottxYV85j1HQFlxmnTq57X1yHY9awZPig06hL/cLMgNWHIQ==", 113 | "dependencies": { 114 | "@types/node": "*", 115 | "@types/qs": "*", 116 | "@types/range-parser": "*", 117 | "@types/send": "*" 118 | } 119 | }, 120 | "node_modules/@types/http-errors": { 121 | "version": "2.0.4", 122 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 123 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" 124 | }, 125 | "node_modules/@types/mime": { 126 | "version": "1.3.5", 127 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 128 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" 129 | }, 130 | "node_modules/@types/node": { 131 | "version": "20.11.10", 132 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz", 133 | "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==", 134 | "dependencies": { 135 | "undici-types": "~5.26.4" 136 | } 137 | }, 138 | "node_modules/@types/qs": { 139 | "version": "6.9.11", 140 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", 141 | "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" 142 | }, 143 | "node_modules/@types/range-parser": { 144 | "version": "1.2.7", 145 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 146 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" 147 | }, 148 | "node_modules/@types/send": { 149 | "version": "0.17.4", 150 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 151 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 152 | "dependencies": { 153 | "@types/mime": "^1", 154 | "@types/node": "*" 155 | } 156 | }, 157 | "node_modules/@types/serve-static": { 158 | "version": "1.15.5", 159 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", 160 | "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", 161 | "dependencies": { 162 | "@types/http-errors": "*", 163 | "@types/mime": "*", 164 | "@types/node": "*" 165 | } 166 | }, 167 | "node_modules/abbrev": { 168 | "version": "1.1.1", 169 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 170 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 171 | }, 172 | "node_modules/accepts": { 173 | "version": "1.3.8", 174 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 175 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 176 | "dependencies": { 177 | "mime-types": "~2.1.34", 178 | "negotiator": "0.6.3" 179 | }, 180 | "engines": { 181 | "node": ">= 0.6" 182 | } 183 | }, 184 | "node_modules/acorn": { 185 | "version": "8.11.3", 186 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 187 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 188 | "bin": { 189 | "acorn": "bin/acorn" 190 | }, 191 | "engines": { 192 | "node": ">=0.4.0" 193 | } 194 | }, 195 | "node_modules/acorn-walk": { 196 | "version": "8.3.2", 197 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 198 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 199 | "engines": { 200 | "node": ">=0.4.0" 201 | } 202 | }, 203 | "node_modules/anymatch": { 204 | "version": "3.1.3", 205 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 206 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 207 | "dependencies": { 208 | "normalize-path": "^3.0.0", 209 | "picomatch": "^2.0.4" 210 | }, 211 | "engines": { 212 | "node": ">= 8" 213 | } 214 | }, 215 | "node_modules/arg": { 216 | "version": "4.1.3", 217 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 218 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 219 | }, 220 | "node_modules/array-flatten": { 221 | "version": "1.1.1", 222 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 223 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 224 | }, 225 | "node_modules/balanced-match": { 226 | "version": "1.0.2", 227 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 228 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 229 | }, 230 | "node_modules/binary-extensions": { 231 | "version": "2.2.0", 232 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 233 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", 234 | "engines": { 235 | "node": ">=8" 236 | } 237 | }, 238 | "node_modules/body-parser": { 239 | "version": "1.20.1", 240 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 241 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 242 | "dependencies": { 243 | "bytes": "3.1.2", 244 | "content-type": "~1.0.4", 245 | "debug": "2.6.9", 246 | "depd": "2.0.0", 247 | "destroy": "1.2.0", 248 | "http-errors": "2.0.0", 249 | "iconv-lite": "0.4.24", 250 | "on-finished": "2.4.1", 251 | "qs": "6.11.0", 252 | "raw-body": "2.5.1", 253 | "type-is": "~1.6.18", 254 | "unpipe": "1.0.0" 255 | }, 256 | "engines": { 257 | "node": ">= 0.8", 258 | "npm": "1.2.8000 || >= 1.4.16" 259 | } 260 | }, 261 | "node_modules/brace-expansion": { 262 | "version": "1.1.11", 263 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 264 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 265 | "dependencies": { 266 | "balanced-match": "^1.0.0", 267 | "concat-map": "0.0.1" 268 | } 269 | }, 270 | "node_modules/braces": { 271 | "version": "3.0.2", 272 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 273 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 274 | "dependencies": { 275 | "fill-range": "^7.0.1" 276 | }, 277 | "engines": { 278 | "node": ">=8" 279 | } 280 | }, 281 | "node_modules/bytes": { 282 | "version": "3.1.2", 283 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 284 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 285 | "engines": { 286 | "node": ">= 0.8" 287 | } 288 | }, 289 | "node_modules/call-bind": { 290 | "version": "1.0.5", 291 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", 292 | "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", 293 | "dependencies": { 294 | "function-bind": "^1.1.2", 295 | "get-intrinsic": "^1.2.1", 296 | "set-function-length": "^1.1.1" 297 | }, 298 | "funding": { 299 | "url": "https://github.com/sponsors/ljharb" 300 | } 301 | }, 302 | "node_modules/chokidar": { 303 | "version": "3.5.3", 304 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 305 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 306 | "funding": [ 307 | { 308 | "type": "individual", 309 | "url": "https://paulmillr.com/funding/" 310 | } 311 | ], 312 | "dependencies": { 313 | "anymatch": "~3.1.2", 314 | "braces": "~3.0.2", 315 | "glob-parent": "~5.1.2", 316 | "is-binary-path": "~2.1.0", 317 | "is-glob": "~4.0.1", 318 | "normalize-path": "~3.0.0", 319 | "readdirp": "~3.6.0" 320 | }, 321 | "engines": { 322 | "node": ">= 8.10.0" 323 | }, 324 | "optionalDependencies": { 325 | "fsevents": "~2.3.2" 326 | } 327 | }, 328 | "node_modules/concat-map": { 329 | "version": "0.0.1", 330 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 331 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 332 | }, 333 | "node_modules/content-disposition": { 334 | "version": "0.5.4", 335 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 336 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 337 | "dependencies": { 338 | "safe-buffer": "5.2.1" 339 | }, 340 | "engines": { 341 | "node": ">= 0.6" 342 | } 343 | }, 344 | "node_modules/content-type": { 345 | "version": "1.0.5", 346 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 347 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 348 | "engines": { 349 | "node": ">= 0.6" 350 | } 351 | }, 352 | "node_modules/cookie": { 353 | "version": "0.5.0", 354 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 355 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", 356 | "engines": { 357 | "node": ">= 0.6" 358 | } 359 | }, 360 | "node_modules/cookie-signature": { 361 | "version": "1.0.6", 362 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 363 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 364 | }, 365 | "node_modules/create-require": { 366 | "version": "1.1.1", 367 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 368 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" 369 | }, 370 | "node_modules/debug": { 371 | "version": "2.6.9", 372 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 373 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 374 | "dependencies": { 375 | "ms": "2.0.0" 376 | } 377 | }, 378 | "node_modules/define-data-property": { 379 | "version": "1.1.1", 380 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", 381 | "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", 382 | "dependencies": { 383 | "get-intrinsic": "^1.2.1", 384 | "gopd": "^1.0.1", 385 | "has-property-descriptors": "^1.0.0" 386 | }, 387 | "engines": { 388 | "node": ">= 0.4" 389 | } 390 | }, 391 | "node_modules/depd": { 392 | "version": "2.0.0", 393 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 394 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 395 | "engines": { 396 | "node": ">= 0.8" 397 | } 398 | }, 399 | "node_modules/destroy": { 400 | "version": "1.2.0", 401 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 402 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 403 | "engines": { 404 | "node": ">= 0.8", 405 | "npm": "1.2.8000 || >= 1.4.16" 406 | } 407 | }, 408 | "node_modules/diff": { 409 | "version": "4.0.2", 410 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 411 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 412 | "engines": { 413 | "node": ">=0.3.1" 414 | } 415 | }, 416 | "node_modules/ee-first": { 417 | "version": "1.1.1", 418 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 419 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 420 | }, 421 | "node_modules/encodeurl": { 422 | "version": "1.0.2", 423 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 424 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 425 | "engines": { 426 | "node": ">= 0.8" 427 | } 428 | }, 429 | "node_modules/es6-promise": { 430 | "version": "4.2.8", 431 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 432 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" 433 | }, 434 | "node_modules/escape-html": { 435 | "version": "1.0.3", 436 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 437 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 438 | }, 439 | "node_modules/etag": { 440 | "version": "1.8.1", 441 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 442 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 443 | "engines": { 444 | "node": ">= 0.6" 445 | } 446 | }, 447 | "node_modules/express": { 448 | "version": "4.18.2", 449 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 450 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 451 | "dependencies": { 452 | "accepts": "~1.3.8", 453 | "array-flatten": "1.1.1", 454 | "body-parser": "1.20.1", 455 | "content-disposition": "0.5.4", 456 | "content-type": "~1.0.4", 457 | "cookie": "0.5.0", 458 | "cookie-signature": "1.0.6", 459 | "debug": "2.6.9", 460 | "depd": "2.0.0", 461 | "encodeurl": "~1.0.2", 462 | "escape-html": "~1.0.3", 463 | "etag": "~1.8.1", 464 | "finalhandler": "1.2.0", 465 | "fresh": "0.5.2", 466 | "http-errors": "2.0.0", 467 | "merge-descriptors": "1.0.1", 468 | "methods": "~1.1.2", 469 | "on-finished": "2.4.1", 470 | "parseurl": "~1.3.3", 471 | "path-to-regexp": "0.1.7", 472 | "proxy-addr": "~2.0.7", 473 | "qs": "6.11.0", 474 | "range-parser": "~1.2.1", 475 | "safe-buffer": "5.2.1", 476 | "send": "0.18.0", 477 | "serve-static": "1.15.0", 478 | "setprototypeof": "1.2.0", 479 | "statuses": "2.0.1", 480 | "type-is": "~1.6.18", 481 | "utils-merge": "1.0.1", 482 | "vary": "~1.1.2" 483 | }, 484 | "engines": { 485 | "node": ">= 0.10.0" 486 | } 487 | }, 488 | "node_modules/express-http-proxy": { 489 | "version": "2.0.0", 490 | "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", 491 | "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", 492 | "dependencies": { 493 | "debug": "^3.0.1", 494 | "es6-promise": "^4.1.1", 495 | "raw-body": "^2.3.0" 496 | }, 497 | "engines": { 498 | "node": ">=6.0.0" 499 | } 500 | }, 501 | "node_modules/express-http-proxy/node_modules/debug": { 502 | "version": "3.2.7", 503 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 504 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 505 | "dependencies": { 506 | "ms": "^2.1.1" 507 | } 508 | }, 509 | "node_modules/express-http-proxy/node_modules/ms": { 510 | "version": "2.1.3", 511 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 512 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 513 | }, 514 | "node_modules/fill-range": { 515 | "version": "7.0.1", 516 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 517 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 518 | "dependencies": { 519 | "to-regex-range": "^5.0.1" 520 | }, 521 | "engines": { 522 | "node": ">=8" 523 | } 524 | }, 525 | "node_modules/finalhandler": { 526 | "version": "1.2.0", 527 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 528 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 529 | "dependencies": { 530 | "debug": "2.6.9", 531 | "encodeurl": "~1.0.2", 532 | "escape-html": "~1.0.3", 533 | "on-finished": "2.4.1", 534 | "parseurl": "~1.3.3", 535 | "statuses": "2.0.1", 536 | "unpipe": "~1.0.0" 537 | }, 538 | "engines": { 539 | "node": ">= 0.8" 540 | } 541 | }, 542 | "node_modules/forwarded": { 543 | "version": "0.2.0", 544 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 545 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 546 | "engines": { 547 | "node": ">= 0.6" 548 | } 549 | }, 550 | "node_modules/fresh": { 551 | "version": "0.5.2", 552 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 553 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 554 | "engines": { 555 | "node": ">= 0.6" 556 | } 557 | }, 558 | "node_modules/fsevents": { 559 | "version": "2.3.3", 560 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 561 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 562 | "hasInstallScript": true, 563 | "optional": true, 564 | "os": [ 565 | "darwin" 566 | ], 567 | "engines": { 568 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 569 | } 570 | }, 571 | "node_modules/function-bind": { 572 | "version": "1.1.2", 573 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 574 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 575 | "funding": { 576 | "url": "https://github.com/sponsors/ljharb" 577 | } 578 | }, 579 | "node_modules/get-intrinsic": { 580 | "version": "1.2.2", 581 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", 582 | "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", 583 | "dependencies": { 584 | "function-bind": "^1.1.2", 585 | "has-proto": "^1.0.1", 586 | "has-symbols": "^1.0.3", 587 | "hasown": "^2.0.0" 588 | }, 589 | "funding": { 590 | "url": "https://github.com/sponsors/ljharb" 591 | } 592 | }, 593 | "node_modules/glob-parent": { 594 | "version": "5.1.2", 595 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 596 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 597 | "dependencies": { 598 | "is-glob": "^4.0.1" 599 | }, 600 | "engines": { 601 | "node": ">= 6" 602 | } 603 | }, 604 | "node_modules/gopd": { 605 | "version": "1.0.1", 606 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 607 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 608 | "dependencies": { 609 | "get-intrinsic": "^1.1.3" 610 | }, 611 | "funding": { 612 | "url": "https://github.com/sponsors/ljharb" 613 | } 614 | }, 615 | "node_modules/has-flag": { 616 | "version": "3.0.0", 617 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 618 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", 619 | "engines": { 620 | "node": ">=4" 621 | } 622 | }, 623 | "node_modules/has-property-descriptors": { 624 | "version": "1.0.1", 625 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", 626 | "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", 627 | "dependencies": { 628 | "get-intrinsic": "^1.2.2" 629 | }, 630 | "funding": { 631 | "url": "https://github.com/sponsors/ljharb" 632 | } 633 | }, 634 | "node_modules/has-proto": { 635 | "version": "1.0.1", 636 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 637 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", 638 | "engines": { 639 | "node": ">= 0.4" 640 | }, 641 | "funding": { 642 | "url": "https://github.com/sponsors/ljharb" 643 | } 644 | }, 645 | "node_modules/has-symbols": { 646 | "version": "1.0.3", 647 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 648 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 649 | "engines": { 650 | "node": ">= 0.4" 651 | }, 652 | "funding": { 653 | "url": "https://github.com/sponsors/ljharb" 654 | } 655 | }, 656 | "node_modules/hasown": { 657 | "version": "2.0.0", 658 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", 659 | "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", 660 | "dependencies": { 661 | "function-bind": "^1.1.2" 662 | }, 663 | "engines": { 664 | "node": ">= 0.4" 665 | } 666 | }, 667 | "node_modules/http-errors": { 668 | "version": "2.0.0", 669 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 670 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 671 | "dependencies": { 672 | "depd": "2.0.0", 673 | "inherits": "2.0.4", 674 | "setprototypeof": "1.2.0", 675 | "statuses": "2.0.1", 676 | "toidentifier": "1.0.1" 677 | }, 678 | "engines": { 679 | "node": ">= 0.8" 680 | } 681 | }, 682 | "node_modules/iconv-lite": { 683 | "version": "0.4.24", 684 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 685 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 686 | "dependencies": { 687 | "safer-buffer": ">= 2.1.2 < 3" 688 | }, 689 | "engines": { 690 | "node": ">=0.10.0" 691 | } 692 | }, 693 | "node_modules/ignore-by-default": { 694 | "version": "1.0.1", 695 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 696 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" 697 | }, 698 | "node_modules/inherits": { 699 | "version": "2.0.4", 700 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 701 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 702 | }, 703 | "node_modules/ipaddr.js": { 704 | "version": "1.9.1", 705 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 706 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 707 | "engines": { 708 | "node": ">= 0.10" 709 | } 710 | }, 711 | "node_modules/is-binary-path": { 712 | "version": "2.1.0", 713 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 714 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 715 | "dependencies": { 716 | "binary-extensions": "^2.0.0" 717 | }, 718 | "engines": { 719 | "node": ">=8" 720 | } 721 | }, 722 | "node_modules/is-extglob": { 723 | "version": "2.1.1", 724 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 725 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 726 | "engines": { 727 | "node": ">=0.10.0" 728 | } 729 | }, 730 | "node_modules/is-glob": { 731 | "version": "4.0.3", 732 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 733 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 734 | "dependencies": { 735 | "is-extglob": "^2.1.1" 736 | }, 737 | "engines": { 738 | "node": ">=0.10.0" 739 | } 740 | }, 741 | "node_modules/is-number": { 742 | "version": "7.0.0", 743 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 744 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 745 | "engines": { 746 | "node": ">=0.12.0" 747 | } 748 | }, 749 | "node_modules/lru-cache": { 750 | "version": "6.0.0", 751 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 752 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 753 | "dependencies": { 754 | "yallist": "^4.0.0" 755 | }, 756 | "engines": { 757 | "node": ">=10" 758 | } 759 | }, 760 | "node_modules/make-error": { 761 | "version": "1.3.6", 762 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 763 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" 764 | }, 765 | "node_modules/media-typer": { 766 | "version": "0.3.0", 767 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 768 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 769 | "engines": { 770 | "node": ">= 0.6" 771 | } 772 | }, 773 | "node_modules/merge-descriptors": { 774 | "version": "1.0.1", 775 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 776 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 777 | }, 778 | "node_modules/methods": { 779 | "version": "1.1.2", 780 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 781 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 782 | "engines": { 783 | "node": ">= 0.6" 784 | } 785 | }, 786 | "node_modules/mime": { 787 | "version": "1.6.0", 788 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 789 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 790 | "bin": { 791 | "mime": "cli.js" 792 | }, 793 | "engines": { 794 | "node": ">=4" 795 | } 796 | }, 797 | "node_modules/mime-db": { 798 | "version": "1.52.0", 799 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 800 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 801 | "engines": { 802 | "node": ">= 0.6" 803 | } 804 | }, 805 | "node_modules/mime-types": { 806 | "version": "2.1.35", 807 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 808 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 809 | "dependencies": { 810 | "mime-db": "1.52.0" 811 | }, 812 | "engines": { 813 | "node": ">= 0.6" 814 | } 815 | }, 816 | "node_modules/minimatch": { 817 | "version": "3.1.2", 818 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 819 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 820 | "dependencies": { 821 | "brace-expansion": "^1.1.7" 822 | }, 823 | "engines": { 824 | "node": "*" 825 | } 826 | }, 827 | "node_modules/ms": { 828 | "version": "2.0.0", 829 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 830 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 831 | }, 832 | "node_modules/negotiator": { 833 | "version": "0.6.3", 834 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 835 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 836 | "engines": { 837 | "node": ">= 0.6" 838 | } 839 | }, 840 | "node_modules/nodemon": { 841 | "version": "3.0.3", 842 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz", 843 | "integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==", 844 | "dependencies": { 845 | "chokidar": "^3.5.2", 846 | "debug": "^4", 847 | "ignore-by-default": "^1.0.1", 848 | "minimatch": "^3.1.2", 849 | "pstree.remy": "^1.1.8", 850 | "semver": "^7.5.3", 851 | "simple-update-notifier": "^2.0.0", 852 | "supports-color": "^5.5.0", 853 | "touch": "^3.1.0", 854 | "undefsafe": "^2.0.5" 855 | }, 856 | "bin": { 857 | "nodemon": "bin/nodemon.js" 858 | }, 859 | "engines": { 860 | "node": ">=10" 861 | }, 862 | "funding": { 863 | "type": "opencollective", 864 | "url": "https://opencollective.com/nodemon" 865 | } 866 | }, 867 | "node_modules/nodemon/node_modules/debug": { 868 | "version": "4.3.4", 869 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 870 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 871 | "dependencies": { 872 | "ms": "2.1.2" 873 | }, 874 | "engines": { 875 | "node": ">=6.0" 876 | }, 877 | "peerDependenciesMeta": { 878 | "supports-color": { 879 | "optional": true 880 | } 881 | } 882 | }, 883 | "node_modules/nodemon/node_modules/ms": { 884 | "version": "2.1.2", 885 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 886 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 887 | }, 888 | "node_modules/nopt": { 889 | "version": "1.0.10", 890 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 891 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 892 | "dependencies": { 893 | "abbrev": "1" 894 | }, 895 | "bin": { 896 | "nopt": "bin/nopt.js" 897 | }, 898 | "engines": { 899 | "node": "*" 900 | } 901 | }, 902 | "node_modules/normalize-path": { 903 | "version": "3.0.0", 904 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 905 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 906 | "engines": { 907 | "node": ">=0.10.0" 908 | } 909 | }, 910 | "node_modules/object-inspect": { 911 | "version": "1.13.1", 912 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 913 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", 914 | "funding": { 915 | "url": "https://github.com/sponsors/ljharb" 916 | } 917 | }, 918 | "node_modules/on-finished": { 919 | "version": "2.4.1", 920 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 921 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 922 | "dependencies": { 923 | "ee-first": "1.1.1" 924 | }, 925 | "engines": { 926 | "node": ">= 0.8" 927 | } 928 | }, 929 | "node_modules/parseurl": { 930 | "version": "1.3.3", 931 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 932 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 933 | "engines": { 934 | "node": ">= 0.8" 935 | } 936 | }, 937 | "node_modules/path-to-regexp": { 938 | "version": "0.1.7", 939 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 940 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 941 | }, 942 | "node_modules/picomatch": { 943 | "version": "2.3.1", 944 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 945 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", 946 | "engines": { 947 | "node": ">=8.6" 948 | }, 949 | "funding": { 950 | "url": "https://github.com/sponsors/jonschlinkert" 951 | } 952 | }, 953 | "node_modules/proxy-addr": { 954 | "version": "2.0.7", 955 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 956 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 957 | "dependencies": { 958 | "forwarded": "0.2.0", 959 | "ipaddr.js": "1.9.1" 960 | }, 961 | "engines": { 962 | "node": ">= 0.10" 963 | } 964 | }, 965 | "node_modules/pstree.remy": { 966 | "version": "1.1.8", 967 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 968 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" 969 | }, 970 | "node_modules/qs": { 971 | "version": "6.11.0", 972 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 973 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 974 | "dependencies": { 975 | "side-channel": "^1.0.4" 976 | }, 977 | "engines": { 978 | "node": ">=0.6" 979 | }, 980 | "funding": { 981 | "url": "https://github.com/sponsors/ljharb" 982 | } 983 | }, 984 | "node_modules/range-parser": { 985 | "version": "1.2.1", 986 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 987 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 988 | "engines": { 989 | "node": ">= 0.6" 990 | } 991 | }, 992 | "node_modules/raw-body": { 993 | "version": "2.5.1", 994 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 995 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 996 | "dependencies": { 997 | "bytes": "3.1.2", 998 | "http-errors": "2.0.0", 999 | "iconv-lite": "0.4.24", 1000 | "unpipe": "1.0.0" 1001 | }, 1002 | "engines": { 1003 | "node": ">= 0.8" 1004 | } 1005 | }, 1006 | "node_modules/readdirp": { 1007 | "version": "3.6.0", 1008 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 1009 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 1010 | "dependencies": { 1011 | "picomatch": "^2.2.1" 1012 | }, 1013 | "engines": { 1014 | "node": ">=8.10.0" 1015 | } 1016 | }, 1017 | "node_modules/safe-buffer": { 1018 | "version": "5.2.1", 1019 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1020 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1021 | "funding": [ 1022 | { 1023 | "type": "github", 1024 | "url": "https://github.com/sponsors/feross" 1025 | }, 1026 | { 1027 | "type": "patreon", 1028 | "url": "https://www.patreon.com/feross" 1029 | }, 1030 | { 1031 | "type": "consulting", 1032 | "url": "https://feross.org/support" 1033 | } 1034 | ] 1035 | }, 1036 | "node_modules/safer-buffer": { 1037 | "version": "2.1.2", 1038 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1039 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1040 | }, 1041 | "node_modules/semver": { 1042 | "version": "7.5.4", 1043 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 1044 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 1045 | "dependencies": { 1046 | "lru-cache": "^6.0.0" 1047 | }, 1048 | "bin": { 1049 | "semver": "bin/semver.js" 1050 | }, 1051 | "engines": { 1052 | "node": ">=10" 1053 | } 1054 | }, 1055 | "node_modules/send": { 1056 | "version": "0.18.0", 1057 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 1058 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 1059 | "dependencies": { 1060 | "debug": "2.6.9", 1061 | "depd": "2.0.0", 1062 | "destroy": "1.2.0", 1063 | "encodeurl": "~1.0.2", 1064 | "escape-html": "~1.0.3", 1065 | "etag": "~1.8.1", 1066 | "fresh": "0.5.2", 1067 | "http-errors": "2.0.0", 1068 | "mime": "1.6.0", 1069 | "ms": "2.1.3", 1070 | "on-finished": "2.4.1", 1071 | "range-parser": "~1.2.1", 1072 | "statuses": "2.0.1" 1073 | }, 1074 | "engines": { 1075 | "node": ">= 0.8.0" 1076 | } 1077 | }, 1078 | "node_modules/send/node_modules/ms": { 1079 | "version": "2.1.3", 1080 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1081 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1082 | }, 1083 | "node_modules/serve-static": { 1084 | "version": "1.15.0", 1085 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 1086 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 1087 | "dependencies": { 1088 | "encodeurl": "~1.0.2", 1089 | "escape-html": "~1.0.3", 1090 | "parseurl": "~1.3.3", 1091 | "send": "0.18.0" 1092 | }, 1093 | "engines": { 1094 | "node": ">= 0.8.0" 1095 | } 1096 | }, 1097 | "node_modules/set-function-length": { 1098 | "version": "1.2.0", 1099 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", 1100 | "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", 1101 | "dependencies": { 1102 | "define-data-property": "^1.1.1", 1103 | "function-bind": "^1.1.2", 1104 | "get-intrinsic": "^1.2.2", 1105 | "gopd": "^1.0.1", 1106 | "has-property-descriptors": "^1.0.1" 1107 | }, 1108 | "engines": { 1109 | "node": ">= 0.4" 1110 | } 1111 | }, 1112 | "node_modules/setprototypeof": { 1113 | "version": "1.2.0", 1114 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1115 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1116 | }, 1117 | "node_modules/side-channel": { 1118 | "version": "1.0.4", 1119 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1120 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1121 | "dependencies": { 1122 | "call-bind": "^1.0.0", 1123 | "get-intrinsic": "^1.0.2", 1124 | "object-inspect": "^1.9.0" 1125 | }, 1126 | "funding": { 1127 | "url": "https://github.com/sponsors/ljharb" 1128 | } 1129 | }, 1130 | "node_modules/simple-update-notifier": { 1131 | "version": "2.0.0", 1132 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 1133 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 1134 | "dependencies": { 1135 | "semver": "^7.5.3" 1136 | }, 1137 | "engines": { 1138 | "node": ">=10" 1139 | } 1140 | }, 1141 | "node_modules/statuses": { 1142 | "version": "2.0.1", 1143 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1144 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1145 | "engines": { 1146 | "node": ">= 0.8" 1147 | } 1148 | }, 1149 | "node_modules/supports-color": { 1150 | "version": "5.5.0", 1151 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1152 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1153 | "dependencies": { 1154 | "has-flag": "^3.0.0" 1155 | }, 1156 | "engines": { 1157 | "node": ">=4" 1158 | } 1159 | }, 1160 | "node_modules/to-regex-range": { 1161 | "version": "5.0.1", 1162 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 1163 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 1164 | "dependencies": { 1165 | "is-number": "^7.0.0" 1166 | }, 1167 | "engines": { 1168 | "node": ">=8.0" 1169 | } 1170 | }, 1171 | "node_modules/toidentifier": { 1172 | "version": "1.0.1", 1173 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1174 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1175 | "engines": { 1176 | "node": ">=0.6" 1177 | } 1178 | }, 1179 | "node_modules/touch": { 1180 | "version": "3.1.0", 1181 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 1182 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 1183 | "dependencies": { 1184 | "nopt": "~1.0.10" 1185 | }, 1186 | "bin": { 1187 | "nodetouch": "bin/nodetouch.js" 1188 | } 1189 | }, 1190 | "node_modules/ts-node": { 1191 | "version": "10.9.2", 1192 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 1193 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 1194 | "dependencies": { 1195 | "@cspotcode/source-map-support": "^0.8.0", 1196 | "@tsconfig/node10": "^1.0.7", 1197 | "@tsconfig/node12": "^1.0.7", 1198 | "@tsconfig/node14": "^1.0.0", 1199 | "@tsconfig/node16": "^1.0.2", 1200 | "acorn": "^8.4.1", 1201 | "acorn-walk": "^8.1.1", 1202 | "arg": "^4.1.0", 1203 | "create-require": "^1.1.0", 1204 | "diff": "^4.0.1", 1205 | "make-error": "^1.1.1", 1206 | "v8-compile-cache-lib": "^3.0.1", 1207 | "yn": "3.1.1" 1208 | }, 1209 | "bin": { 1210 | "ts-node": "dist/bin.js", 1211 | "ts-node-cwd": "dist/bin-cwd.js", 1212 | "ts-node-esm": "dist/bin-esm.js", 1213 | "ts-node-script": "dist/bin-script.js", 1214 | "ts-node-transpile-only": "dist/bin-transpile.js", 1215 | "ts-script": "dist/bin-script-deprecated.js" 1216 | }, 1217 | "peerDependencies": { 1218 | "@swc/core": ">=1.2.50", 1219 | "@swc/wasm": ">=1.2.50", 1220 | "@types/node": "*", 1221 | "typescript": ">=2.7" 1222 | }, 1223 | "peerDependenciesMeta": { 1224 | "@swc/core": { 1225 | "optional": true 1226 | }, 1227 | "@swc/wasm": { 1228 | "optional": true 1229 | } 1230 | } 1231 | }, 1232 | "node_modules/type-is": { 1233 | "version": "1.6.18", 1234 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1235 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1236 | "dependencies": { 1237 | "media-typer": "0.3.0", 1238 | "mime-types": "~2.1.24" 1239 | }, 1240 | "engines": { 1241 | "node": ">= 0.6" 1242 | } 1243 | }, 1244 | "node_modules/typescript": { 1245 | "version": "5.3.3", 1246 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 1247 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 1248 | "peer": true, 1249 | "bin": { 1250 | "tsc": "bin/tsc", 1251 | "tsserver": "bin/tsserver" 1252 | }, 1253 | "engines": { 1254 | "node": ">=14.17" 1255 | } 1256 | }, 1257 | "node_modules/undefsafe": { 1258 | "version": "2.0.5", 1259 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 1260 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" 1261 | }, 1262 | "node_modules/undici-types": { 1263 | "version": "5.26.5", 1264 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1265 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 1266 | }, 1267 | "node_modules/unpipe": { 1268 | "version": "1.0.0", 1269 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1270 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1271 | "engines": { 1272 | "node": ">= 0.8" 1273 | } 1274 | }, 1275 | "node_modules/utils-merge": { 1276 | "version": "1.0.1", 1277 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1278 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 1279 | "engines": { 1280 | "node": ">= 0.4.0" 1281 | } 1282 | }, 1283 | "node_modules/v8-compile-cache-lib": { 1284 | "version": "3.0.1", 1285 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 1286 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" 1287 | }, 1288 | "node_modules/vary": { 1289 | "version": "1.1.2", 1290 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1291 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1292 | "engines": { 1293 | "node": ">= 0.8" 1294 | } 1295 | }, 1296 | "node_modules/yallist": { 1297 | "version": "4.0.0", 1298 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1299 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1300 | }, 1301 | "node_modules/yn": { 1302 | "version": "3.1.1", 1303 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 1304 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 1305 | "engines": { 1306 | "node": ">=6" 1307 | } 1308 | } 1309 | }, 1310 | "dependencies": { 1311 | "@cspotcode/source-map-support": { 1312 | "version": "0.8.1", 1313 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 1314 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 1315 | "requires": { 1316 | "@jridgewell/trace-mapping": "0.3.9" 1317 | } 1318 | }, 1319 | "@jridgewell/resolve-uri": { 1320 | "version": "3.1.1", 1321 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 1322 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==" 1323 | }, 1324 | "@jridgewell/sourcemap-codec": { 1325 | "version": "1.4.15", 1326 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 1327 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" 1328 | }, 1329 | "@jridgewell/trace-mapping": { 1330 | "version": "0.3.9", 1331 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 1332 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 1333 | "requires": { 1334 | "@jridgewell/resolve-uri": "^3.0.3", 1335 | "@jridgewell/sourcemap-codec": "^1.4.10" 1336 | } 1337 | }, 1338 | "@tsconfig/node10": { 1339 | "version": "1.0.9", 1340 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 1341 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==" 1342 | }, 1343 | "@tsconfig/node12": { 1344 | "version": "1.0.11", 1345 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 1346 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==" 1347 | }, 1348 | "@tsconfig/node14": { 1349 | "version": "1.0.3", 1350 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 1351 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==" 1352 | }, 1353 | "@tsconfig/node16": { 1354 | "version": "1.0.4", 1355 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 1356 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==" 1357 | }, 1358 | "@types/body-parser": { 1359 | "version": "1.19.5", 1360 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", 1361 | "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", 1362 | "requires": { 1363 | "@types/connect": "*", 1364 | "@types/node": "*" 1365 | } 1366 | }, 1367 | "@types/connect": { 1368 | "version": "3.4.38", 1369 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 1370 | "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 1371 | "requires": { 1372 | "@types/node": "*" 1373 | } 1374 | }, 1375 | "@types/express": { 1376 | "version": "4.17.21", 1377 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", 1378 | "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", 1379 | "requires": { 1380 | "@types/body-parser": "*", 1381 | "@types/express-serve-static-core": "^4.17.33", 1382 | "@types/qs": "*", 1383 | "@types/serve-static": "*" 1384 | } 1385 | }, 1386 | "@types/express-http-proxy": { 1387 | "version": "1.6.6", 1388 | "resolved": "https://registry.npmjs.org/@types/express-http-proxy/-/express-http-proxy-1.6.6.tgz", 1389 | "integrity": "sha512-J8ZqHG76rq1UB716IZ3RCmUhg406pbWxsM3oFCFccl5xlWUPzoR4if6Og/cE4juK8emH0H9quZa5ltn6ZdmQJg==", 1390 | "requires": { 1391 | "@types/express": "*" 1392 | } 1393 | }, 1394 | "@types/express-serve-static-core": { 1395 | "version": "4.17.42", 1396 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.42.tgz", 1397 | "integrity": "sha512-ckM3jm2bf/MfB3+spLPWYPUH573plBFwpOhqQ2WottxYV85j1HQFlxmnTq57X1yHY9awZPig06hL/cLMgNWHIQ==", 1398 | "requires": { 1399 | "@types/node": "*", 1400 | "@types/qs": "*", 1401 | "@types/range-parser": "*", 1402 | "@types/send": "*" 1403 | } 1404 | }, 1405 | "@types/http-errors": { 1406 | "version": "2.0.4", 1407 | "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", 1408 | "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==" 1409 | }, 1410 | "@types/mime": { 1411 | "version": "1.3.5", 1412 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 1413 | "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" 1414 | }, 1415 | "@types/node": { 1416 | "version": "20.11.10", 1417 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz", 1418 | "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==", 1419 | "requires": { 1420 | "undici-types": "~5.26.4" 1421 | } 1422 | }, 1423 | "@types/qs": { 1424 | "version": "6.9.11", 1425 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", 1426 | "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==" 1427 | }, 1428 | "@types/range-parser": { 1429 | "version": "1.2.7", 1430 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 1431 | "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" 1432 | }, 1433 | "@types/send": { 1434 | "version": "0.17.4", 1435 | "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", 1436 | "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", 1437 | "requires": { 1438 | "@types/mime": "^1", 1439 | "@types/node": "*" 1440 | } 1441 | }, 1442 | "@types/serve-static": { 1443 | "version": "1.15.5", 1444 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", 1445 | "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", 1446 | "requires": { 1447 | "@types/http-errors": "*", 1448 | "@types/mime": "*", 1449 | "@types/node": "*" 1450 | } 1451 | }, 1452 | "abbrev": { 1453 | "version": "1.1.1", 1454 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 1455 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" 1456 | }, 1457 | "accepts": { 1458 | "version": "1.3.8", 1459 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 1460 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 1461 | "requires": { 1462 | "mime-types": "~2.1.34", 1463 | "negotiator": "0.6.3" 1464 | } 1465 | }, 1466 | "acorn": { 1467 | "version": "8.11.3", 1468 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 1469 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==" 1470 | }, 1471 | "acorn-walk": { 1472 | "version": "8.3.2", 1473 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 1474 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==" 1475 | }, 1476 | "anymatch": { 1477 | "version": "3.1.3", 1478 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", 1479 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", 1480 | "requires": { 1481 | "normalize-path": "^3.0.0", 1482 | "picomatch": "^2.0.4" 1483 | } 1484 | }, 1485 | "arg": { 1486 | "version": "4.1.3", 1487 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 1488 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==" 1489 | }, 1490 | "array-flatten": { 1491 | "version": "1.1.1", 1492 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 1493 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" 1494 | }, 1495 | "balanced-match": { 1496 | "version": "1.0.2", 1497 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 1498 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 1499 | }, 1500 | "binary-extensions": { 1501 | "version": "2.2.0", 1502 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", 1503 | "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" 1504 | }, 1505 | "body-parser": { 1506 | "version": "1.20.1", 1507 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", 1508 | "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", 1509 | "requires": { 1510 | "bytes": "3.1.2", 1511 | "content-type": "~1.0.4", 1512 | "debug": "2.6.9", 1513 | "depd": "2.0.0", 1514 | "destroy": "1.2.0", 1515 | "http-errors": "2.0.0", 1516 | "iconv-lite": "0.4.24", 1517 | "on-finished": "2.4.1", 1518 | "qs": "6.11.0", 1519 | "raw-body": "2.5.1", 1520 | "type-is": "~1.6.18", 1521 | "unpipe": "1.0.0" 1522 | } 1523 | }, 1524 | "brace-expansion": { 1525 | "version": "1.1.11", 1526 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 1527 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 1528 | "requires": { 1529 | "balanced-match": "^1.0.0", 1530 | "concat-map": "0.0.1" 1531 | } 1532 | }, 1533 | "braces": { 1534 | "version": "3.0.2", 1535 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 1536 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 1537 | "requires": { 1538 | "fill-range": "^7.0.1" 1539 | } 1540 | }, 1541 | "bytes": { 1542 | "version": "3.1.2", 1543 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 1544 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 1545 | }, 1546 | "call-bind": { 1547 | "version": "1.0.5", 1548 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", 1549 | "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", 1550 | "requires": { 1551 | "function-bind": "^1.1.2", 1552 | "get-intrinsic": "^1.2.1", 1553 | "set-function-length": "^1.1.1" 1554 | } 1555 | }, 1556 | "chokidar": { 1557 | "version": "3.5.3", 1558 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", 1559 | "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", 1560 | "requires": { 1561 | "anymatch": "~3.1.2", 1562 | "braces": "~3.0.2", 1563 | "fsevents": "~2.3.2", 1564 | "glob-parent": "~5.1.2", 1565 | "is-binary-path": "~2.1.0", 1566 | "is-glob": "~4.0.1", 1567 | "normalize-path": "~3.0.0", 1568 | "readdirp": "~3.6.0" 1569 | } 1570 | }, 1571 | "concat-map": { 1572 | "version": "0.0.1", 1573 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 1574 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 1575 | }, 1576 | "content-disposition": { 1577 | "version": "0.5.4", 1578 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 1579 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 1580 | "requires": { 1581 | "safe-buffer": "5.2.1" 1582 | } 1583 | }, 1584 | "content-type": { 1585 | "version": "1.0.5", 1586 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 1587 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" 1588 | }, 1589 | "cookie": { 1590 | "version": "0.5.0", 1591 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", 1592 | "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" 1593 | }, 1594 | "cookie-signature": { 1595 | "version": "1.0.6", 1596 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 1597 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" 1598 | }, 1599 | "create-require": { 1600 | "version": "1.1.1", 1601 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 1602 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==" 1603 | }, 1604 | "debug": { 1605 | "version": "2.6.9", 1606 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1607 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1608 | "requires": { 1609 | "ms": "2.0.0" 1610 | } 1611 | }, 1612 | "define-data-property": { 1613 | "version": "1.1.1", 1614 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", 1615 | "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", 1616 | "requires": { 1617 | "get-intrinsic": "^1.2.1", 1618 | "gopd": "^1.0.1", 1619 | "has-property-descriptors": "^1.0.0" 1620 | } 1621 | }, 1622 | "depd": { 1623 | "version": "2.0.0", 1624 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 1625 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 1626 | }, 1627 | "destroy": { 1628 | "version": "1.2.0", 1629 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 1630 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 1631 | }, 1632 | "diff": { 1633 | "version": "4.0.2", 1634 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 1635 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" 1636 | }, 1637 | "ee-first": { 1638 | "version": "1.1.1", 1639 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 1640 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" 1641 | }, 1642 | "encodeurl": { 1643 | "version": "1.0.2", 1644 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1645 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" 1646 | }, 1647 | "es6-promise": { 1648 | "version": "4.2.8", 1649 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 1650 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" 1651 | }, 1652 | "escape-html": { 1653 | "version": "1.0.3", 1654 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1655 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 1656 | }, 1657 | "etag": { 1658 | "version": "1.8.1", 1659 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1660 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 1661 | }, 1662 | "express": { 1663 | "version": "4.18.2", 1664 | "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", 1665 | "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", 1666 | "requires": { 1667 | "accepts": "~1.3.8", 1668 | "array-flatten": "1.1.1", 1669 | "body-parser": "1.20.1", 1670 | "content-disposition": "0.5.4", 1671 | "content-type": "~1.0.4", 1672 | "cookie": "0.5.0", 1673 | "cookie-signature": "1.0.6", 1674 | "debug": "2.6.9", 1675 | "depd": "2.0.0", 1676 | "encodeurl": "~1.0.2", 1677 | "escape-html": "~1.0.3", 1678 | "etag": "~1.8.1", 1679 | "finalhandler": "1.2.0", 1680 | "fresh": "0.5.2", 1681 | "http-errors": "2.0.0", 1682 | "merge-descriptors": "1.0.1", 1683 | "methods": "~1.1.2", 1684 | "on-finished": "2.4.1", 1685 | "parseurl": "~1.3.3", 1686 | "path-to-regexp": "0.1.7", 1687 | "proxy-addr": "~2.0.7", 1688 | "qs": "6.11.0", 1689 | "range-parser": "~1.2.1", 1690 | "safe-buffer": "5.2.1", 1691 | "send": "0.18.0", 1692 | "serve-static": "1.15.0", 1693 | "setprototypeof": "1.2.0", 1694 | "statuses": "2.0.1", 1695 | "type-is": "~1.6.18", 1696 | "utils-merge": "1.0.1", 1697 | "vary": "~1.1.2" 1698 | } 1699 | }, 1700 | "express-http-proxy": { 1701 | "version": "2.0.0", 1702 | "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", 1703 | "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", 1704 | "requires": { 1705 | "debug": "^3.0.1", 1706 | "es6-promise": "^4.1.1", 1707 | "raw-body": "^2.3.0" 1708 | }, 1709 | "dependencies": { 1710 | "debug": { 1711 | "version": "3.2.7", 1712 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 1713 | "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 1714 | "requires": { 1715 | "ms": "^2.1.1" 1716 | } 1717 | }, 1718 | "ms": { 1719 | "version": "2.1.3", 1720 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1721 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1722 | } 1723 | } 1724 | }, 1725 | "fill-range": { 1726 | "version": "7.0.1", 1727 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 1728 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 1729 | "requires": { 1730 | "to-regex-range": "^5.0.1" 1731 | } 1732 | }, 1733 | "finalhandler": { 1734 | "version": "1.2.0", 1735 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", 1736 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", 1737 | "requires": { 1738 | "debug": "2.6.9", 1739 | "encodeurl": "~1.0.2", 1740 | "escape-html": "~1.0.3", 1741 | "on-finished": "2.4.1", 1742 | "parseurl": "~1.3.3", 1743 | "statuses": "2.0.1", 1744 | "unpipe": "~1.0.0" 1745 | } 1746 | }, 1747 | "forwarded": { 1748 | "version": "0.2.0", 1749 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1750 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 1751 | }, 1752 | "fresh": { 1753 | "version": "0.5.2", 1754 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1755 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" 1756 | }, 1757 | "fsevents": { 1758 | "version": "2.3.3", 1759 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 1760 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 1761 | "optional": true 1762 | }, 1763 | "function-bind": { 1764 | "version": "1.1.2", 1765 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1766 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" 1767 | }, 1768 | "get-intrinsic": { 1769 | "version": "1.2.2", 1770 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", 1771 | "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", 1772 | "requires": { 1773 | "function-bind": "^1.1.2", 1774 | "has-proto": "^1.0.1", 1775 | "has-symbols": "^1.0.3", 1776 | "hasown": "^2.0.0" 1777 | } 1778 | }, 1779 | "glob-parent": { 1780 | "version": "5.1.2", 1781 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 1782 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 1783 | "requires": { 1784 | "is-glob": "^4.0.1" 1785 | } 1786 | }, 1787 | "gopd": { 1788 | "version": "1.0.1", 1789 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 1790 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 1791 | "requires": { 1792 | "get-intrinsic": "^1.1.3" 1793 | } 1794 | }, 1795 | "has-flag": { 1796 | "version": "3.0.0", 1797 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1798 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" 1799 | }, 1800 | "has-property-descriptors": { 1801 | "version": "1.0.1", 1802 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", 1803 | "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", 1804 | "requires": { 1805 | "get-intrinsic": "^1.2.2" 1806 | } 1807 | }, 1808 | "has-proto": { 1809 | "version": "1.0.1", 1810 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 1811 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" 1812 | }, 1813 | "has-symbols": { 1814 | "version": "1.0.3", 1815 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 1816 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 1817 | }, 1818 | "hasown": { 1819 | "version": "2.0.0", 1820 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", 1821 | "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", 1822 | "requires": { 1823 | "function-bind": "^1.1.2" 1824 | } 1825 | }, 1826 | "http-errors": { 1827 | "version": "2.0.0", 1828 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1829 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1830 | "requires": { 1831 | "depd": "2.0.0", 1832 | "inherits": "2.0.4", 1833 | "setprototypeof": "1.2.0", 1834 | "statuses": "2.0.1", 1835 | "toidentifier": "1.0.1" 1836 | } 1837 | }, 1838 | "iconv-lite": { 1839 | "version": "0.4.24", 1840 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 1841 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1842 | "requires": { 1843 | "safer-buffer": ">= 2.1.2 < 3" 1844 | } 1845 | }, 1846 | "ignore-by-default": { 1847 | "version": "1.0.1", 1848 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", 1849 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" 1850 | }, 1851 | "inherits": { 1852 | "version": "2.0.4", 1853 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1854 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 1855 | }, 1856 | "ipaddr.js": { 1857 | "version": "1.9.1", 1858 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1859 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 1860 | }, 1861 | "is-binary-path": { 1862 | "version": "2.1.0", 1863 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 1864 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 1865 | "requires": { 1866 | "binary-extensions": "^2.0.0" 1867 | } 1868 | }, 1869 | "is-extglob": { 1870 | "version": "2.1.1", 1871 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 1872 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" 1873 | }, 1874 | "is-glob": { 1875 | "version": "4.0.3", 1876 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 1877 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 1878 | "requires": { 1879 | "is-extglob": "^2.1.1" 1880 | } 1881 | }, 1882 | "is-number": { 1883 | "version": "7.0.0", 1884 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 1885 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 1886 | }, 1887 | "lru-cache": { 1888 | "version": "6.0.0", 1889 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 1890 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 1891 | "requires": { 1892 | "yallist": "^4.0.0" 1893 | } 1894 | }, 1895 | "make-error": { 1896 | "version": "1.3.6", 1897 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 1898 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==" 1899 | }, 1900 | "media-typer": { 1901 | "version": "0.3.0", 1902 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 1903 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" 1904 | }, 1905 | "merge-descriptors": { 1906 | "version": "1.0.1", 1907 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 1908 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" 1909 | }, 1910 | "methods": { 1911 | "version": "1.1.2", 1912 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 1913 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" 1914 | }, 1915 | "mime": { 1916 | "version": "1.6.0", 1917 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 1918 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 1919 | }, 1920 | "mime-db": { 1921 | "version": "1.52.0", 1922 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 1923 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 1924 | }, 1925 | "mime-types": { 1926 | "version": "2.1.35", 1927 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 1928 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 1929 | "requires": { 1930 | "mime-db": "1.52.0" 1931 | } 1932 | }, 1933 | "minimatch": { 1934 | "version": "3.1.2", 1935 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1936 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1937 | "requires": { 1938 | "brace-expansion": "^1.1.7" 1939 | } 1940 | }, 1941 | "ms": { 1942 | "version": "2.0.0", 1943 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1944 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" 1945 | }, 1946 | "negotiator": { 1947 | "version": "0.6.3", 1948 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 1949 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 1950 | }, 1951 | "nodemon": { 1952 | "version": "3.0.3", 1953 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.3.tgz", 1954 | "integrity": "sha512-7jH/NXbFPxVaMwmBCC2B9F/V6X1VkEdNgx3iu9jji8WxWcvhMWkmhNWhI5077zknOnZnBzba9hZP6bCPJLSReQ==", 1955 | "requires": { 1956 | "chokidar": "^3.5.2", 1957 | "debug": "^4", 1958 | "ignore-by-default": "^1.0.1", 1959 | "minimatch": "^3.1.2", 1960 | "pstree.remy": "^1.1.8", 1961 | "semver": "^7.5.3", 1962 | "simple-update-notifier": "^2.0.0", 1963 | "supports-color": "^5.5.0", 1964 | "touch": "^3.1.0", 1965 | "undefsafe": "^2.0.5" 1966 | }, 1967 | "dependencies": { 1968 | "debug": { 1969 | "version": "4.3.4", 1970 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 1971 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 1972 | "requires": { 1973 | "ms": "2.1.2" 1974 | } 1975 | }, 1976 | "ms": { 1977 | "version": "2.1.2", 1978 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1979 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 1980 | } 1981 | } 1982 | }, 1983 | "nopt": { 1984 | "version": "1.0.10", 1985 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", 1986 | "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", 1987 | "requires": { 1988 | "abbrev": "1" 1989 | } 1990 | }, 1991 | "normalize-path": { 1992 | "version": "3.0.0", 1993 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 1994 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" 1995 | }, 1996 | "object-inspect": { 1997 | "version": "1.13.1", 1998 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", 1999 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" 2000 | }, 2001 | "on-finished": { 2002 | "version": "2.4.1", 2003 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 2004 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 2005 | "requires": { 2006 | "ee-first": "1.1.1" 2007 | } 2008 | }, 2009 | "parseurl": { 2010 | "version": "1.3.3", 2011 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 2012 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 2013 | }, 2014 | "path-to-regexp": { 2015 | "version": "0.1.7", 2016 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 2017 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" 2018 | }, 2019 | "picomatch": { 2020 | "version": "2.3.1", 2021 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", 2022 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" 2023 | }, 2024 | "proxy-addr": { 2025 | "version": "2.0.7", 2026 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 2027 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 2028 | "requires": { 2029 | "forwarded": "0.2.0", 2030 | "ipaddr.js": "1.9.1" 2031 | } 2032 | }, 2033 | "pstree.remy": { 2034 | "version": "1.1.8", 2035 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", 2036 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" 2037 | }, 2038 | "qs": { 2039 | "version": "6.11.0", 2040 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", 2041 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", 2042 | "requires": { 2043 | "side-channel": "^1.0.4" 2044 | } 2045 | }, 2046 | "range-parser": { 2047 | "version": "1.2.1", 2048 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 2049 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 2050 | }, 2051 | "raw-body": { 2052 | "version": "2.5.1", 2053 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 2054 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 2055 | "requires": { 2056 | "bytes": "3.1.2", 2057 | "http-errors": "2.0.0", 2058 | "iconv-lite": "0.4.24", 2059 | "unpipe": "1.0.0" 2060 | } 2061 | }, 2062 | "readdirp": { 2063 | "version": "3.6.0", 2064 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", 2065 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", 2066 | "requires": { 2067 | "picomatch": "^2.2.1" 2068 | } 2069 | }, 2070 | "safe-buffer": { 2071 | "version": "5.2.1", 2072 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 2073 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 2074 | }, 2075 | "safer-buffer": { 2076 | "version": "2.1.2", 2077 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 2078 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 2079 | }, 2080 | "semver": { 2081 | "version": "7.5.4", 2082 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", 2083 | "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", 2084 | "requires": { 2085 | "lru-cache": "^6.0.0" 2086 | } 2087 | }, 2088 | "send": { 2089 | "version": "0.18.0", 2090 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", 2091 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", 2092 | "requires": { 2093 | "debug": "2.6.9", 2094 | "depd": "2.0.0", 2095 | "destroy": "1.2.0", 2096 | "encodeurl": "~1.0.2", 2097 | "escape-html": "~1.0.3", 2098 | "etag": "~1.8.1", 2099 | "fresh": "0.5.2", 2100 | "http-errors": "2.0.0", 2101 | "mime": "1.6.0", 2102 | "ms": "2.1.3", 2103 | "on-finished": "2.4.1", 2104 | "range-parser": "~1.2.1", 2105 | "statuses": "2.0.1" 2106 | }, 2107 | "dependencies": { 2108 | "ms": { 2109 | "version": "2.1.3", 2110 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2111 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 2112 | } 2113 | } 2114 | }, 2115 | "serve-static": { 2116 | "version": "1.15.0", 2117 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", 2118 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", 2119 | "requires": { 2120 | "encodeurl": "~1.0.2", 2121 | "escape-html": "~1.0.3", 2122 | "parseurl": "~1.3.3", 2123 | "send": "0.18.0" 2124 | } 2125 | }, 2126 | "set-function-length": { 2127 | "version": "1.2.0", 2128 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.0.tgz", 2129 | "integrity": "sha512-4DBHDoyHlM1IRPGYcoxexgh67y4ueR53FKV1yyxwFMY7aCqcN/38M1+SwZ/qJQ8iLv7+ck385ot4CcisOAPT9w==", 2130 | "requires": { 2131 | "define-data-property": "^1.1.1", 2132 | "function-bind": "^1.1.2", 2133 | "get-intrinsic": "^1.2.2", 2134 | "gopd": "^1.0.1", 2135 | "has-property-descriptors": "^1.0.1" 2136 | } 2137 | }, 2138 | "setprototypeof": { 2139 | "version": "1.2.0", 2140 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 2141 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 2142 | }, 2143 | "side-channel": { 2144 | "version": "1.0.4", 2145 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 2146 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 2147 | "requires": { 2148 | "call-bind": "^1.0.0", 2149 | "get-intrinsic": "^1.0.2", 2150 | "object-inspect": "^1.9.0" 2151 | } 2152 | }, 2153 | "simple-update-notifier": { 2154 | "version": "2.0.0", 2155 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", 2156 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", 2157 | "requires": { 2158 | "semver": "^7.5.3" 2159 | } 2160 | }, 2161 | "statuses": { 2162 | "version": "2.0.1", 2163 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 2164 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 2165 | }, 2166 | "supports-color": { 2167 | "version": "5.5.0", 2168 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 2169 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 2170 | "requires": { 2171 | "has-flag": "^3.0.0" 2172 | } 2173 | }, 2174 | "to-regex-range": { 2175 | "version": "5.0.1", 2176 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 2177 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 2178 | "requires": { 2179 | "is-number": "^7.0.0" 2180 | } 2181 | }, 2182 | "toidentifier": { 2183 | "version": "1.0.1", 2184 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 2185 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 2186 | }, 2187 | "touch": { 2188 | "version": "3.1.0", 2189 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", 2190 | "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", 2191 | "requires": { 2192 | "nopt": "~1.0.10" 2193 | } 2194 | }, 2195 | "ts-node": { 2196 | "version": "10.9.2", 2197 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 2198 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 2199 | "requires": { 2200 | "@cspotcode/source-map-support": "^0.8.0", 2201 | "@tsconfig/node10": "^1.0.7", 2202 | "@tsconfig/node12": "^1.0.7", 2203 | "@tsconfig/node14": "^1.0.0", 2204 | "@tsconfig/node16": "^1.0.2", 2205 | "acorn": "^8.4.1", 2206 | "acorn-walk": "^8.1.1", 2207 | "arg": "^4.1.0", 2208 | "create-require": "^1.1.0", 2209 | "diff": "^4.0.1", 2210 | "make-error": "^1.1.1", 2211 | "v8-compile-cache-lib": "^3.0.1", 2212 | "yn": "3.1.1" 2213 | } 2214 | }, 2215 | "type-is": { 2216 | "version": "1.6.18", 2217 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 2218 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 2219 | "requires": { 2220 | "media-typer": "0.3.0", 2221 | "mime-types": "~2.1.24" 2222 | } 2223 | }, 2224 | "typescript": { 2225 | "version": "5.3.3", 2226 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 2227 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 2228 | "peer": true 2229 | }, 2230 | "undefsafe": { 2231 | "version": "2.0.5", 2232 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", 2233 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" 2234 | }, 2235 | "undici-types": { 2236 | "version": "5.26.5", 2237 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 2238 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" 2239 | }, 2240 | "unpipe": { 2241 | "version": "1.0.0", 2242 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2243 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" 2244 | }, 2245 | "utils-merge": { 2246 | "version": "1.0.1", 2247 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2248 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" 2249 | }, 2250 | "v8-compile-cache-lib": { 2251 | "version": "3.0.1", 2252 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 2253 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==" 2254 | }, 2255 | "vary": { 2256 | "version": "1.1.2", 2257 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2258 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" 2259 | }, 2260 | "yallist": { 2261 | "version": "4.0.0", 2262 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 2263 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 2264 | }, 2265 | "yn": { 2266 | "version": "3.1.1", 2267 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 2268 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" 2269 | } 2270 | } 2271 | } 2272 | -------------------------------------------------------------------------------- /gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gateway", 3 | "version": "1.0.0", 4 | "description": "API Gateway - Chat Server", 5 | "main": "index.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "dev": "nodemon index.ts" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/davydocsurg/chat-server.git" 13 | }, 14 | "keywords": [ 15 | "typescript", 16 | "microservices", 17 | "javascript", 18 | "docker", 19 | "mongodb" 20 | ], 21 | "author": "davydocsurg", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/davydocsurg/chat-server/issues" 25 | }, 26 | "homepage": "https://github.com/davydocsurg/chat-server#readme", 27 | "dependencies": { 28 | "@types/express": "^4.17.21", 29 | "@types/express-http-proxy": "^1.6.6", 30 | "express": "^4.18.2", 31 | "express-http-proxy": "^2.0.0", 32 | "nodemon": "^3.0.3", 33 | "ts-node": "^10.9.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:latest 2 | 3 | RUN rm /etc/nginx/nginx.conf 4 | 5 | COPY nginx.conf /etc/nginx/nginx.conf 6 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | http { 2 | upstream user { 3 | server user:8081; 4 | } 5 | upstream chat { 6 | server chat:8082; 7 | } 8 | upstream notification { 9 | server notification:8083; 10 | } 11 | 12 | server { 13 | listen 85; 14 | 15 | location /user/ { 16 | proxy_pass http://user/; 17 | } 18 | 19 | location /chat/ { 20 | proxy_pass http://chat/; 21 | } 22 | 23 | location /notification/ { 24 | proxy_pass http://notification/; 25 | } 26 | } 27 | } 28 | events {} 29 | -------------------------------------------------------------------------------- /notification-service/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile 4 | .dockerignore 5 | .git 6 | .gitignore 7 | -------------------------------------------------------------------------------- /notification-service/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | PORT=8083 3 | MESSAGE_BROKER_URL="{{YOUR_MESSAGE_BROKER_URL}}" 4 | SENDINBLUE_APIKEY="{{YOUR_SENDINBLUE_APIKEY}}" 5 | SMTP_HOST="smtp-relay.brevo.com" 6 | SMTP_PORT=587 7 | SMTP_USER="{{YOUR_SENDINBLUE_ACCOUNT_EMAIL}}" 8 | SMTP_PASS="{{YOUR_SENDINBLUE_PASSWORD}}" 9 | EMAIL_FROM="{{YOUR_EMAIL_SOURCE}}" -------------------------------------------------------------------------------- /notification-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | EXPOSE 8083 14 | 15 | CMD [ "npm", "start"] 16 | -------------------------------------------------------------------------------- /notification-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notification-service", 3 | "version": "1.0.0", 4 | "description": "Notification Service - Chat Server", 5 | "main": "src/server.ts", 6 | "scripts": { 7 | "dev": "NODE_ENV=development nodemon src/server.ts", 8 | "build": "rm -rf build/ && tsc -p .", 9 | "start": "NODE_ENV=production nodemon build/server.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/davydocsurg/chat-server.git" 15 | }, 16 | "keywords": [ 17 | "typescript", 18 | "microservices", 19 | "javascript", 20 | "docker", 21 | "mongodb" 22 | ], 23 | "author": "davydocsurg", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/davydocsurg/chat-server/issues" 27 | }, 28 | "homepage": "https://github.com/davydocsurg/chat-server#readme", 29 | "dependencies": { 30 | "amqplib": "^0.10.3", 31 | "dotenv": "^16.4.1", 32 | "express": "^4.18.2", 33 | "firebase-admin": "^12.0.0", 34 | "nodemailer": "^6.9.8", 35 | "nodemon": "^3.0.3", 36 | "sib-api-v3-typescript": "^2.2.2", 37 | "ts-node": "^10.9.2", 38 | "typescript": "^5.3.3" 39 | }, 40 | "devDependencies": { 41 | "@types/amqplib": "^0.10.4", 42 | "@types/express": "^4.17.21", 43 | "@types/nodemailer": "^6.4.14" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /notification-service/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | 3 | const configFile = `./.env`; 4 | config({ path: configFile }); 5 | 6 | const { 7 | PORT, 8 | JWT_SECRET, 9 | NODE_ENV, 10 | MESSAGE_BROKER_URL, 11 | SENDINBLUE_APIKEY, 12 | EMAIL_FROM, 13 | SMTP_HOST, 14 | SMTP_PORT = 587, 15 | SMTP_USER, 16 | SMTP_PASS, 17 | } = process.env; 18 | 19 | const queue = { notifications: "NOTIFICATIONS" }; 20 | 21 | export default { 22 | PORT, 23 | JWT_SECRET, 24 | env: NODE_ENV, 25 | msgBrokerURL: MESSAGE_BROKER_URL, 26 | SENDINBLUE_APIKEY, 27 | EMAIL_FROM, 28 | queue, 29 | smtp: { 30 | host: SMTP_HOST, 31 | port: SMTP_PORT as number, 32 | user: SMTP_USER, 33 | pass: SMTP_PASS, 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /notification-service/src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler } from "express"; 2 | import { ApiError } from "../utils"; 3 | 4 | export const errorConverter: ErrorRequestHandler = (err, req, res, next) => { 5 | let error = err; 6 | if (!(error instanceof ApiError)) { 7 | const statusCode = 8 | error.statusCode || 9 | (error instanceof Error 10 | ? 400 // Bad Request 11 | : 500); // Internal Server Error 12 | const message = 13 | error.message || 14 | (statusCode === 400 ? "Bad Request" : "Internal Server Error"); 15 | error = new ApiError(statusCode, message, false, err.stack.toString()); 16 | } 17 | next(error); 18 | }; 19 | 20 | export const errorHandler: ErrorRequestHandler = (err, req, res, next) => { 21 | let { statusCode, message } = err; 22 | if (process.env.NODE_ENV === "production" && !err.isOperational) { 23 | statusCode = 500; // Internal Server Error 24 | message = "Internal Server Error"; 25 | } 26 | 27 | res.locals.errorMessage = err.message; 28 | 29 | const response = { 30 | code: statusCode, 31 | message, 32 | ...(process.env.NODE_ENV === "development" && { stack: err.stack }), 33 | }; 34 | 35 | if (process.env.NODE_ENV === "development") { 36 | console.error(err); 37 | } 38 | 39 | res.status(statusCode).json(response); 40 | next(); 41 | }; 42 | -------------------------------------------------------------------------------- /notification-service/src/server.ts: -------------------------------------------------------------------------------- 1 | import express, { Express } from "express"; 2 | import { Server } from "http"; 3 | import { errorConverter, errorHandler } from "./middleware"; 4 | import config from "./config/config"; 5 | import { rabbitMQService } from "./services/RabbitMQService"; 6 | 7 | const app: Express = express(); 8 | let server: Server; 9 | app.use(express.json()); 10 | app.use(express.urlencoded({ extended: true })); 11 | app.use(errorConverter); 12 | app.use(errorHandler); 13 | 14 | server = app.listen(config.PORT, () => { 15 | console.log(`Server is running on port ${config.PORT}`); 16 | }); 17 | 18 | const initializeRabbitMQClient = async () => { 19 | try { 20 | await rabbitMQService.init(); 21 | console.log("RabbitMQ client initialized and listening for messages."); 22 | } catch (err) { 23 | console.error("Failed to initialize RabbitMQ client:", err); 24 | } 25 | }; 26 | 27 | initializeRabbitMQClient(); 28 | 29 | const exitHandler = () => { 30 | if (server) { 31 | server.close(() => { 32 | console.info("Server closed"); 33 | process.exit(1); 34 | }); 35 | } else { 36 | process.exit(1); 37 | } 38 | }; 39 | 40 | const unexpectedErrorHandler = (error: unknown) => { 41 | console.error(error); 42 | exitHandler(); 43 | }; 44 | 45 | process.on("uncaughtException", unexpectedErrorHandler); 46 | process.on("unhandledRejection", unexpectedErrorHandler); 47 | -------------------------------------------------------------------------------- /notification-service/src/services/EmailService.ts: -------------------------------------------------------------------------------- 1 | import nodemailer from "nodemailer"; 2 | import config from "../config/config"; 3 | 4 | export class EmailService { 5 | private transporter; 6 | 7 | constructor() { 8 | this.transporter = nodemailer.createTransport({ 9 | host: config.smtp.host, 10 | port: config.smtp.port, 11 | secure: false, 12 | auth: { 13 | user: config.smtp.user, 14 | pass: config.smtp.pass, 15 | }, 16 | }); 17 | } 18 | 19 | async sendEmail(to: string, subject: string, content: string) { 20 | const mailOptions = { 21 | from: config.EMAIL_FROM, 22 | to: to, 23 | subject: subject, 24 | html: content, 25 | }; 26 | 27 | try { 28 | const info = await this.transporter.sendMail(mailOptions); 29 | console.log("Email sent: %s", info.messageId); 30 | } catch (error) { 31 | console.error("Error sending email:", error); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /notification-service/src/services/FCMService.ts: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | 3 | admin.initializeApp({ 4 | credential: admin.credential.applicationDefault(), 5 | }); 6 | 7 | export class FCMService { 8 | async sendPushNotification(token: string, message: string) { 9 | const payload = { 10 | notification: { 11 | title: "New Message", 12 | body: message, 13 | }, 14 | token: token, 15 | }; 16 | 17 | try { 18 | await admin.messaging().send(payload); 19 | console.log("Notification sent successfully"); 20 | } catch (error) { 21 | console.error("Error sending notification", error); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /notification-service/src/services/RabbitMQService.ts: -------------------------------------------------------------------------------- 1 | import amqp, { Channel } from "amqplib"; 2 | import config from "../config/config"; 3 | import { FCMService } from "./FCMService"; 4 | import { EmailService } from "./EmailService"; 5 | import { UserStatusStore } from "../utils"; 6 | 7 | class RabbitMQService { 8 | private channel!: Channel; 9 | private fcmService = new FCMService(); 10 | private emailService = new EmailService(); 11 | private userStatusStore = new UserStatusStore(); 12 | 13 | constructor() { 14 | this.init(); 15 | } 16 | 17 | async init() { 18 | const connection = await amqp.connect(config.msgBrokerURL!); 19 | this.channel = await connection.createChannel(); 20 | await this.consumeNotification(); 21 | } 22 | 23 | async consumeNotification() { 24 | await this.channel.assertQueue(config.queue.notifications); 25 | this.channel.consume(config.queue.notifications, async (msg) => { 26 | if (msg) { 27 | const { 28 | type, 29 | userId, 30 | message, 31 | userEmail, 32 | userToken, 33 | fromName, 34 | } = JSON.parse(msg.content.toString()); 35 | 36 | if (type === "MESSAGE_RECEIVED") { 37 | // Check if the user is online 38 | const isUserOnline = 39 | this.userStatusStore.isUserOnline(userId); 40 | 41 | if (isUserOnline && userToken) { 42 | // User is online, send a push notification 43 | await this.fcmService.sendPushNotification( 44 | userToken, 45 | message 46 | ); 47 | } else if (userEmail) { 48 | // User is offline, send an email 49 | await this.emailService.sendEmail( 50 | userEmail, 51 | `New Message from ${fromName}`, 52 | message 53 | ); 54 | } 55 | } 56 | 57 | this.channel.ack(msg); // Acknowledge the message after processing 58 | } 59 | }); 60 | } 61 | } 62 | 63 | export const rabbitMQService = new RabbitMQService(); 64 | -------------------------------------------------------------------------------- /notification-service/src/services/index.ts: -------------------------------------------------------------------------------- 1 | import { FCMService } from "./FCMService"; 2 | import { EmailService } from "./EmailService"; 3 | 4 | export { FCMService, EmailService }; 5 | -------------------------------------------------------------------------------- /notification-service/src/utils/apiError.ts: -------------------------------------------------------------------------------- 1 | class ApiError extends Error { 2 | statusCode: number; 3 | isOperational: boolean; 4 | 5 | constructor( 6 | statusCode: number, 7 | message: string | undefined, 8 | isOperational = true, 9 | stack = "" 10 | ) { 11 | super(message); 12 | this.statusCode = statusCode; 13 | this.isOperational = isOperational; 14 | if (stack) { 15 | this.stack = stack; 16 | } else { 17 | Error.captureStackTrace(this, this.constructor); 18 | } 19 | } 20 | } 21 | 22 | export { ApiError }; 23 | -------------------------------------------------------------------------------- /notification-service/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { UserStatusStore } from "./userStatusStore"; 2 | import { ApiError } from "./apiError"; 3 | 4 | export { UserStatusStore, ApiError }; 5 | -------------------------------------------------------------------------------- /notification-service/src/utils/userStatusStore.ts: -------------------------------------------------------------------------------- 1 | export class UserStatusStore { 2 | private static instance: UserStatusStore; 3 | private userStatuses: Record; 4 | 5 | constructor() { 6 | this.userStatuses = {}; 7 | } 8 | 9 | public static getInstance(): UserStatusStore { 10 | if (!UserStatusStore.instance) { 11 | UserStatusStore.instance = new UserStatusStore(); 12 | } 13 | return UserStatusStore.instance; 14 | } 15 | 16 | setUserOnline(userId: string) { 17 | this.userStatuses[userId] = true; 18 | } 19 | 20 | setUserOffline(userId: string) { 21 | this.userStatuses[userId] = false; 22 | } 23 | 24 | isUserOnline(userId: string): boolean { 25 | return !!this.userStatuses[userId]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /notification-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./build", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "rootDir": "./src" 10 | }, 11 | "include": [ 12 | "./src/**/*", 13 | ], 14 | "exclude": [ 15 | "node_modules", 16 | "tests" 17 | ] 18 | } -------------------------------------------------------------------------------- /user-service/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile 4 | .dockerignore 5 | .git 6 | .gitignore 7 | -------------------------------------------------------------------------------- /user-service/.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV="development" 2 | PORT=8081 3 | JWT_SECRET="{{YOUR_JWT_SECRET}}" 4 | MESSAGE_BROKER_URL="{{YOUR_MESSAGE_BROKER_URL}}" 5 | MONGO_URI="{{YOUR_MONGODB_URI}}" -------------------------------------------------------------------------------- /user-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | EXPOSE 8081 14 | 15 | CMD [ "npm", "start"] 16 | -------------------------------------------------------------------------------- /user-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-service", 3 | "version": "1.0.0", 4 | "description": "User Service - Chat Server", 5 | "main": "src/server.ts", 6 | "scripts": { 7 | "dev": "NODE_ENV=development nodemon src/server.ts", 8 | "build": "rm -rf build/ && tsc -p .", 9 | "start": "NODE_ENV=production nodemon build/server.js", 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/davydocsurg/chat-server.git" 15 | }, 16 | "keywords": [ 17 | "typescript", 18 | "microservices", 19 | "javascript", 20 | "docker", 21 | "mongodb" 22 | ], 23 | "author": "davydocsurg", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/davydocsurg/chat-server/issues" 27 | }, 28 | "homepage": "https://github.com/davydocsurg/chat-server#readme", 29 | "dependencies": { 30 | "@types/express": "^4.17.21", 31 | "@types/validator": "^13.11.8", 32 | "amqplib": "^0.10.3", 33 | "bcryptjs": "^2.4.3", 34 | "dotenv": "^16.3.1", 35 | "express": "^4.18.2", 36 | "jsonwebtoken": "^9.0.2", 37 | "mongoose": "^8.0.4", 38 | "nodemon": "^3.0.2", 39 | "ts-node": "^10.9.2", 40 | "typescript": "^5.3.3", 41 | "validator": "^13.11.0" 42 | }, 43 | "devDependencies": { 44 | "@types/amqplib": "^0.10.4", 45 | "@types/bcryptjs": "^2.4.6", 46 | "@types/jsonwebtoken": "^9.0.5" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /user-service/src/config/config.ts: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | 3 | const configFile = `./.env`; 4 | config({ path: configFile }); 5 | 6 | const { MONGO_URI, PORT, JWT_SECRET, NODE_ENV, MESSAGE_BROKER_URL } = 7 | process.env; 8 | 9 | export default { 10 | MONGO_URI, 11 | PORT, 12 | JWT_SECRET, 13 | env: NODE_ENV, 14 | msgBrokerURL: MESSAGE_BROKER_URL, 15 | }; 16 | -------------------------------------------------------------------------------- /user-service/src/controllers/AuthController.ts: -------------------------------------------------------------------------------- 1 | import express, { Request, Response } from "express"; 2 | import jwt from "jsonwebtoken"; 3 | import { User } from "../database"; 4 | import { ApiError, encryptPassword, isPasswordMatch } from "../utils"; 5 | import config from "../config/config"; 6 | import { IUser } from "../database"; 7 | 8 | const jwtSecret = config.JWT_SECRET as string; 9 | const COOKIE_EXPIRATION_DAYS = 90; // cookie expiration in days 10 | const expirationDate = new Date( 11 | Date.now() + COOKIE_EXPIRATION_DAYS * 24 * 60 * 60 * 1000 12 | ); 13 | const cookieOptions = { 14 | expires: expirationDate, 15 | secure: false, 16 | httpOnly: true, 17 | }; 18 | 19 | const register = async (req: Request, res: Response) => { 20 | try { 21 | const { name, email, password } = req.body; 22 | const userExists = await User.findOne({ email }); 23 | if (userExists) { 24 | throw new ApiError(400, "User already exists!"); 25 | } 26 | 27 | const user = await User.create({ 28 | name, 29 | email, 30 | password: await encryptPassword(password), 31 | }); 32 | 33 | const userData = { 34 | id: user._id, 35 | name: user.name, 36 | email: user.email, 37 | }; 38 | 39 | return res.json({ 40 | status: 200, 41 | message: "User registered successfully!", 42 | data: userData, 43 | }); 44 | } catch (error: any) { 45 | return res.json({ 46 | status: 500, 47 | message: error.message, 48 | }); 49 | } 50 | }; 51 | 52 | const createSendToken = async (user: IUser, res: Response) => { 53 | const { name, email, id } = user; 54 | const token = jwt.sign({ name, email, id }, jwtSecret, { 55 | expiresIn: "1d", 56 | }); 57 | if (config.env === "production") cookieOptions.secure = true; 58 | res.cookie("jwt", token, cookieOptions); 59 | 60 | return token; 61 | }; 62 | 63 | const login = async (req: Request, res: Response) => { 64 | try { 65 | const { email, password } = req.body; 66 | const user = await User.findOne({ email }).select("+password"); 67 | if ( 68 | !user || 69 | !(await isPasswordMatch(password, user.password as string)) 70 | ) { 71 | throw new ApiError(400, "Incorrect email or password"); 72 | } 73 | 74 | const token = await createSendToken(user!, res); 75 | 76 | return res.json({ 77 | status: 200, 78 | message: "User logged in successfully!", 79 | token, 80 | }); 81 | } catch (error: any) { 82 | return res.json({ 83 | status: 500, 84 | message: error.message, 85 | }); 86 | } 87 | }; 88 | 89 | export default { 90 | register, 91 | login, 92 | }; 93 | -------------------------------------------------------------------------------- /user-service/src/database/connection.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import config from "../config/config"; 3 | 4 | export const connectDB = async () => { 5 | try { 6 | console.info("Connecting to database..." + config.MONGO_URI); 7 | await mongoose.connect(config.MONGO_URI!); 8 | console.info("Database connected"); 9 | } catch (error) { 10 | console.error(error); 11 | process.exit(1); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /user-service/src/database/index.ts: -------------------------------------------------------------------------------- 1 | import User, { IUser } from "./models/UserModel"; 2 | import { connectDB } from "./connection"; 3 | 4 | export { User, IUser, connectDB }; 5 | -------------------------------------------------------------------------------- /user-service/src/database/models/UserModel.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Schema, Document } from "mongoose"; 2 | import validator from "validator"; 3 | 4 | export interface IUser extends Document { 5 | name: string; 6 | email: string; 7 | password: string; 8 | createdAt: Date; 9 | updatedAt: Date; 10 | } 11 | 12 | const UserSchema: Schema = new Schema( 13 | { 14 | name: { 15 | type: String, 16 | trim: true, 17 | required: [true, "Name must be provided"], 18 | minlength: 3, 19 | }, 20 | email: { 21 | type: String, 22 | required: true, 23 | unique: true, 24 | lowercase: true, 25 | trim: true, 26 | validate: [validator.isEmail, "Please provide a valid email."], 27 | }, 28 | password: { 29 | type: String, 30 | trim: false, 31 | required: [true, "Password must be provided"], 32 | minlength: 8, 33 | }, 34 | }, 35 | { 36 | timestamps: true, 37 | } 38 | ); 39 | 40 | const User = mongoose.model("User", UserSchema); 41 | export default User; 42 | -------------------------------------------------------------------------------- /user-service/src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler } from "express"; 2 | import { ApiError } from "../utils"; 3 | 4 | export const errorConverter: ErrorRequestHandler = (err, req, res, next) => { 5 | let error = err; 6 | if (!(error instanceof ApiError)) { 7 | const statusCode = 8 | error.statusCode || 9 | (error instanceof Error 10 | ? 400 // Bad Request 11 | : 500); // Internal Server Error 12 | const message = 13 | error.message || 14 | (statusCode === 400 ? "Bad Request" : "Internal Server Error"); 15 | error = new ApiError(statusCode, message, false, err.stack.toString()); 16 | } 17 | next(error); 18 | }; 19 | 20 | export const errorHandler: ErrorRequestHandler = (err, req, res, next) => { 21 | let { statusCode, message } = err; 22 | if (process.env.NODE_ENV === "production" && !err.isOperational) { 23 | statusCode = 500; // Internal Server Error 24 | message = "Internal Server Error"; 25 | } 26 | 27 | res.locals.errorMessage = err.message; 28 | 29 | const response = { 30 | code: statusCode, 31 | message, 32 | ...(process.env.NODE_ENV === "development" && { stack: err.stack }), 33 | }; 34 | 35 | if (process.env.NODE_ENV === "development") { 36 | console.error(err); 37 | } 38 | 39 | res.status(statusCode).json(response); 40 | next(); 41 | }; 42 | -------------------------------------------------------------------------------- /user-service/src/routes/authRoutes.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import AuthController from "../controllers/AuthController"; 3 | 4 | const userRouter = Router(); 5 | 6 | userRouter.post("/register", AuthController.register); 7 | userRouter.post("/login", AuthController.login); 8 | 9 | export default userRouter; 10 | -------------------------------------------------------------------------------- /user-service/src/server.ts: -------------------------------------------------------------------------------- 1 | import express, { Express } from "express"; 2 | import { Server } from "http"; 3 | import userRouter from "./routes/authRoutes"; 4 | import { errorConverter, errorHandler } from "./middleware"; 5 | import { connectDB } from "./database"; 6 | import config from "./config/config"; 7 | import { rabbitMQService } from "./services/RabbitMQService"; 8 | 9 | const app: Express = express(); 10 | let server: Server; 11 | app.use(express.json()); 12 | app.use(express.urlencoded({ extended: true })); 13 | app.use(userRouter); 14 | app.use(errorConverter); 15 | app.use(errorHandler); 16 | 17 | connectDB(); 18 | 19 | server = app.listen(config.PORT, () => { 20 | console.log(`Server is running on port ${config.PORT}`); 21 | }); 22 | 23 | const initializeRabbitMQClient = async () => { 24 | try { 25 | await rabbitMQService.init(); 26 | console.log("RabbitMQ client initialized and listening for messages."); 27 | } catch (err) { 28 | console.error("Failed to initialize RabbitMQ client:", err); 29 | } 30 | }; 31 | 32 | initializeRabbitMQClient(); 33 | 34 | const exitHandler = () => { 35 | if (server) { 36 | server.close(() => { 37 | console.info("Server closed"); 38 | process.exit(1); 39 | }); 40 | } else { 41 | process.exit(1); 42 | } 43 | }; 44 | 45 | const unexpectedErrorHandler = (error: unknown) => { 46 | console.error(error); 47 | exitHandler(); 48 | }; 49 | 50 | process.on("uncaughtException", unexpectedErrorHandler); 51 | process.on("unhandledRejection", unexpectedErrorHandler); 52 | -------------------------------------------------------------------------------- /user-service/src/services/RabbitMQService.ts: -------------------------------------------------------------------------------- 1 | import amqp, { Channel, Connection } from "amqplib"; 2 | import config from "../config/config"; 3 | import { User } from "../database"; 4 | import { ApiError } from "../utils"; 5 | 6 | class RabbitMQService { 7 | private requestQueue = "USER_DETAILS_REQUEST"; 8 | private responseQueue = "USER_DETAILS_RESPONSE"; 9 | private connection!: Connection; 10 | private channel!: Channel; 11 | 12 | constructor() { 13 | this.init(); 14 | } 15 | 16 | async init() { 17 | // Establish connection to RabbitMQ server 18 | this.connection = await amqp.connect(config.msgBrokerURL!); 19 | this.channel = await this.connection.createChannel(); 20 | 21 | // Asserting queues ensures they exist 22 | await this.channel.assertQueue(this.requestQueue); 23 | await this.channel.assertQueue(this.responseQueue); 24 | 25 | // Start listening for messages on the request queue 26 | this.listenForRequests(); 27 | } 28 | 29 | private async listenForRequests() { 30 | this.channel.consume(this.requestQueue, async (msg) => { 31 | if (msg && msg.content) { 32 | const { userId } = JSON.parse(msg.content.toString()); 33 | const userDetails = await getUserDetails(userId); 34 | 35 | // Send the user details response 36 | this.channel.sendToQueue( 37 | this.responseQueue, 38 | Buffer.from(JSON.stringify(userDetails)), 39 | { correlationId: msg.properties.correlationId } 40 | ); 41 | 42 | // Acknowledge the processed message 43 | this.channel.ack(msg); 44 | } 45 | }); 46 | } 47 | } 48 | 49 | const getUserDetails = async (userId: string) => { 50 | const userDetails = await User.findById(userId).select("-password"); 51 | if (!userDetails) { 52 | throw new ApiError(404, "User not found"); 53 | } 54 | 55 | return userDetails; 56 | }; 57 | export const rabbitMQService = new RabbitMQService(); 58 | -------------------------------------------------------------------------------- /user-service/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | class ApiError extends Error { 3 | statusCode: number; 4 | isOperational: boolean; 5 | 6 | constructor( 7 | statusCode: number, 8 | message: string | undefined, 9 | isOperational = true, 10 | stack = "" 11 | ) { 12 | super(message); 13 | this.statusCode = statusCode; 14 | this.isOperational = isOperational; 15 | if (stack) { 16 | this.stack = stack; 17 | } else { 18 | Error.captureStackTrace(this, this.constructor); 19 | } 20 | } 21 | } 22 | 23 | const encryptPassword = async (password: string) => { 24 | const encryptedPassword = await bcrypt.hash(password, 12); 25 | return encryptedPassword; 26 | }; 27 | 28 | const isPasswordMatch = async (password: string, userPassword: string) => { 29 | const result = await bcrypt.compare(password, userPassword); 30 | return result; 31 | }; 32 | 33 | export { ApiError, encryptPassword, isPasswordMatch }; 34 | -------------------------------------------------------------------------------- /user-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./build", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true 9 | }, 10 | "include": [ 11 | "src/**/*" 12 | ], 13 | "exclude": [ 14 | "node_modules", 15 | "tests" 16 | ] 17 | } --------------------------------------------------------------------------------