├── api-gateway ├── src │ ├── routes │ │ ├── inventory.controller.ts │ │ ├── users.controller.ts │ │ ├── orders.controller.ts │ │ └── products.controller.ts │ ├── app.service.ts │ ├── main.ts │ ├── app.controller.ts │ ├── app.module.ts │ ├── proxy │ │ ├── proxy.service.spec.ts │ │ ├── proxy.service.ts │ │ └── jwt-auth.guard.ts │ └── app.controller.spec.ts ├── .prettierrc ├── tsconfig.build.json ├── Dockerfile ├── README.md ├── nest-cli.json ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts ├── tsconfig.json ├── .gitignore ├── eslint.config.mjs └── package.json ├── order-service ├── .prettierrc ├── tsconfig.build.json ├── README.md ├── Dockerfile ├── src │ ├── app.service.ts │ ├── product │ │ ├── product.controller.ts │ │ ├── product.entity.ts │ │ ├── product.module.ts │ │ ├── product.service.spec.ts │ │ ├── product.controller.spec.ts │ │ └── product.service.ts │ ├── app.controller.ts │ ├── main.ts │ ├── order │ │ ├── order.entity.ts │ │ ├── order.service.spec.ts │ │ ├── order.controller.spec.ts │ │ ├── order.module.ts │ │ ├── order.controller.ts │ │ └── order.service.ts │ ├── rabbitmq │ │ ├── rabbitmq.service.spec.ts │ │ └── rabbitmq.service.ts │ ├── utils │ │ └── rabbitmq.ts │ ├── app.controller.spec.ts │ └── app.module.ts ├── nest-cli.json ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts ├── tsconfig.json ├── .gitignore ├── eslint.config.mjs └── package.json ├── user-service ├── .prettierrc ├── tsconfig.build.json ├── Dockerfile ├── README.md ├── src │ ├── app.service.ts │ ├── auth │ │ ├── jwt-auth.guard.ts │ │ ├── auth.controller.ts │ │ ├── auth.service.spec.ts │ │ ├── jwt.strategy.ts │ │ ├── auth.controller.spec.ts │ │ ├── auth.module.ts │ │ └── auth.service.ts │ ├── user │ │ ├── dto │ │ │ └── create-user.dto.ts │ │ ├── user.entity.ts │ │ ├── user.module.ts │ │ ├── user.service.spec.ts │ │ ├── user.controller.spec.ts │ │ ├── user.controller.ts │ │ └── user.service.ts │ ├── app.controller.ts │ ├── main.ts │ ├── app.controller.spec.ts │ └── app.module.ts ├── nest-cli.json ├── test │ ├── jest-e2e.json │ └── app.e2e-spec.ts ├── tsconfig.json ├── .gitignore ├── eslint.config.mjs └── package.json ├── inventory-service ├── Dockerfile ├── README.md ├── src │ ├── models │ │ └── product.js │ ├── db.js │ └── queue │ │ └── consumer.js ├── package.json ├── index.js └── .gitignore ├── notification-service ├── Dockerfile ├── README.md ├── index.js ├── package.json ├── src │ └── queue │ │ └── consumer.js ├── .gitignore └── package-lock.json ├── product-service ├── Dockerfile ├── README.md ├── src │ ├── models │ │ └── product.model.js │ ├── routes │ │ └── product.routes.js │ ├── validators │ │ └── product.validator.js │ ├── utils │ │ └── rabbitmq.js │ └── controllers │ │ └── product.controller.js ├── package.json ├── index.js ├── .gitignore └── package-lock.json ├── product-event-listener ├── Dockerfile ├── package.json ├── index.js ├── package-lock.json └── .gitignore ├── order-event-listener ├── Dockerfile ├── package.json ├── index.js └── package-lock.json ├── .dependencygraph └── setting.json ├── nginx └── conf.d │ └── default.conf ├── package.json ├── start-all.sh ├── .gitignore ├── docker-compose.yml └── README.md /api-gateway/src/routes/inventory.controller.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /api-gateway/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /order-service/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /user-service/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all" 4 | } -------------------------------------------------------------------------------- /api-gateway/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /order-service/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /user-service/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /order-service/README.md: -------------------------------------------------------------------------------- 1 | #### 🛒 `order-service` (NestJS + PostgreSQL) 2 | 3 | * Place Order 4 | * Validate user & product availability 5 | * Emit event `order_created` 6 | -------------------------------------------------------------------------------- /inventory-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine3.22 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | EXPOSE 3000 7 | CMD ["node", "index.js"] 8 | -------------------------------------------------------------------------------- /inventory-service/README.md: -------------------------------------------------------------------------------- 1 | #### 🏬 `inventory-service` (ExpressJS + MongoDB) 2 | 3 | * Listen to `order_created` 4 | * Decrease stock 5 | * Reject order if stock not enough 6 | -------------------------------------------------------------------------------- /notification-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | EXPOSE 3000 7 | CMD ["node", "index.js"] 8 | -------------------------------------------------------------------------------- /product-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine3.22 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | EXPOSE 3000 7 | CMD ["node", "index.js"] 8 | -------------------------------------------------------------------------------- /api-gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine3.22 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | RUN npm run build 7 | CMD ["node", "dist/main"] 8 | -------------------------------------------------------------------------------- /notification-service/README.md: -------------------------------------------------------------------------------- 1 | #### 🔔 `notification-service` (ExpressJS + MongoDB) 2 | 3 | * Listen to `order_created` or `order_confirmed` 4 | * Log or simulate email sending 5 | -------------------------------------------------------------------------------- /order-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine3.22 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | RUN npm run build 7 | CMD ["node", "dist/main"] 8 | -------------------------------------------------------------------------------- /product-event-listener/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | WORKDIR /app 4 | COPY package*.json ./ 5 | RUN npm install 6 | COPY . . 7 | EXPOSE 3000 8 | CMD ["node", "index.js"] 9 | -------------------------------------------------------------------------------- /product-service/README.md: -------------------------------------------------------------------------------- 1 | #### 📦 `product-service` (ExpressJS + MongoDB) 2 | 3 | * Create / List / Delete products 4 | * Fetch product by ID 5 | * Store simple document schema 6 | -------------------------------------------------------------------------------- /user-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine3.22 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | RUN npm run build 7 | CMD ["node", "dist/main"] 8 | -------------------------------------------------------------------------------- /order-event-listener/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | WORKDIR /usr/src/app 4 | COPY package*.json ./ 5 | RUN npm install 6 | COPY . . 7 | EXPOSE 3000 8 | CMD ["node", "index.js"] 9 | -------------------------------------------------------------------------------- /api-gateway/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /order-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /user-service/README.md: -------------------------------------------------------------------------------- 1 | #### 🔐 `user-service` (NestJS + PostgreSQL): 2 | * Sign up / Login 3 | * JWT Authentication 4 | * Roles (admin, user) 5 | 6 | 7 | 8 | 9 | $ sudo docker-compose up --build -d -------------------------------------------------------------------------------- /user-service/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | 3 | @Injectable() 4 | export class AppService { 5 | getHello(): string { 6 | return 'Hello World!'; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /user-service/src/auth/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | 4 | @Injectable() 5 | export class JwtAuthGuard extends AuthGuard('jwt') {} -------------------------------------------------------------------------------- /api-gateway/README.md: -------------------------------------------------------------------------------- 1 | #### 🌐 `api-gateway` (ExpressJS or NestJS) 2 | 3 | * Single entrypoint for frontend 4 | * Reverse proxy + token verification 5 | * Route `/products`, `/orders`, `/users` to respective services 6 | -------------------------------------------------------------------------------- /api-gateway/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /order-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /user-service/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "deleteOutDir": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /user-service/src/user/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail, IsNotEmpty, MinLength } from 'class-validator'; 2 | 3 | export class CreateUserDto { 4 | @IsEmail() 5 | email: string; 6 | 7 | @MinLength(6) 8 | password: string; 9 | } -------------------------------------------------------------------------------- /.dependencygraph/setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryFilePath": "index.js", 3 | "alias": false, 4 | "resolveExtensions": [ 5 | ".js", 6 | ".jsx", 7 | ".ts", 8 | ".tsx", 9 | ".vue", 10 | ".scss", 11 | ".less" 12 | ] 13 | } -------------------------------------------------------------------------------- /api-gateway/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /order-service/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /user-service/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /api-gateway/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | 4 | async function bootstrap() { 5 | const app = await NestFactory.create(AppModule); 6 | await app.listen(process.env.PORT ?? 3011); 7 | } 8 | bootstrap(); 9 | -------------------------------------------------------------------------------- /order-service/src/product/product.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller } from '@nestjs/common'; 2 | import { ProductService } from './product.service'; 3 | 4 | @Controller('product') 5 | export class ProductController { 6 | constructor(private productService: ProductService) {} 7 | } 8 | -------------------------------------------------------------------------------- /inventory-service/src/models/product.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const productSchema = new mongoose.Schema({ 4 | _id: String, 5 | name: String, 6 | price: Number, 7 | stock: Number, 8 | }); 9 | 10 | export const Product = mongoose.model('Product', productSchema); 11 | -------------------------------------------------------------------------------- /product-service/src/models/product.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const ProductSchema = new mongoose.Schema({ 4 | name: String, 5 | price: Number, 6 | inStock: Boolean, 7 | }, { timestamps: true }); 8 | 9 | module.exports = mongoose.model('Product', ProductSchema); 10 | -------------------------------------------------------------------------------- /order-service/src/product/product.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryColumn, Column } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Product { 5 | @PrimaryColumn() 6 | id: string; 7 | 8 | @Column() 9 | name: string; 10 | 11 | @Column('decimal') 12 | price: number; 13 | 14 | @Column() 15 | inStock: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /api-gateway/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /order-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /user-service/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get } from '@nestjs/common'; 2 | import { AppService } from './app.service'; 3 | 4 | @Controller() 5 | export class AppController { 6 | constructor(private readonly appService: AppService) {} 7 | 8 | @Get() 9 | getHello(): string { 10 | return this.appService.getHello(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /order-service/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { AppModule } from './app.module'; 3 | import { connectRabbitMQ } from './utils/rabbitmq'; 4 | 5 | async function bootstrap() { 6 | await connectRabbitMQ(); 7 | const app = await NestFactory.create(AppModule); 8 | await app.listen(process.env.PORT ?? 3009); 9 | } 10 | bootstrap(); 11 | -------------------------------------------------------------------------------- /user-service/src/user/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'; 2 | 3 | @Entity() 4 | export class User { 5 | @PrimaryGeneratedColumn() 6 | id: number; 7 | 8 | @Column({ unique: true }) 9 | email: string; 10 | 11 | @Column() 12 | password: string; 13 | 14 | @Column({ default: 'user' }) 15 | role: string; 16 | } 17 | -------------------------------------------------------------------------------- /inventory-service/src/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | import dotenv from 'dotenv'; 3 | dotenv.config(); 4 | 5 | export async function connectDB() { 6 | try { 7 | await mongoose.connect(process.env.MONGO_URI); 8 | console.log('✅ Connected to MongoDB [inventory-service]'); 9 | } catch (err) { 10 | console.error('❌ MongoDB connection error:', err); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /order-service/src/order/order.entity.ts: -------------------------------------------------------------------------------- 1 | import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn } from 'typeorm'; 2 | 3 | @Entity() 4 | export class Order { 5 | @PrimaryGeneratedColumn('uuid') 6 | id: string; 7 | 8 | @Column() 9 | productId: string; 10 | 11 | @Column() 12 | userId: string; 13 | 14 | @Column('int') 15 | quantity: number; 16 | 17 | @CreateDateColumn() 18 | createdAt: Date; 19 | } 20 | -------------------------------------------------------------------------------- /order-event-listener/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order-event-listener", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "type": "commonjs", 13 | "dependencies": { 14 | "amqplib": "^0.10.8", 15 | "dotenv": "^16.5.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /product-event-listener/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product-event-listener", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "type": "commonjs", 13 | "dependencies": { 14 | "amqplib": "^0.10.8", 15 | "dotenv": "^16.5.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /product-service/src/routes/product.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | const controller = require('../controllers/product.controller'); 4 | 5 | router.get('/', controller.getAll); 6 | router.post('/', controller.create); 7 | router.patch('/:id', controller.update); 8 | router.get('/:id', controller.getById); 9 | router.delete('/:id', controller.delete); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /notification-service/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import dotenv from 'dotenv'; 3 | import { startConsumer } from './src/queue/consumer.js'; 4 | 5 | dotenv.config(); 6 | const app = express(); 7 | 8 | app.get('/', (req, res) => { 9 | res.send('🔔 Notification service running'); 10 | }); 11 | 12 | app.listen(process.env.PORT, async () => { 13 | console.log(`🚀 Notification service running on port ${process.env.PORT}`); 14 | await startConsumer(); 15 | }); 16 | -------------------------------------------------------------------------------- /user-service/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { User } from './user.entity'; 4 | import { UserService } from './user.service'; 5 | import { UserController } from './user.controller'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([User])], 9 | providers: [UserService], 10 | controllers: [UserController], 11 | exports: [UserService], 12 | }) 13 | export class UserModule {} 14 | -------------------------------------------------------------------------------- /inventory-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "inventory-service", 3 | "version": "1.0.0", 4 | "description": "* Listen to `order_created` * Decrease stock * Reject order if stock not enough", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "start": "node index.js" 9 | }, 10 | "dependencies": { 11 | "amqplib": "^0.10.3", 12 | "dotenv": "^16.0.3", 13 | "express": "^4.18.2", 14 | "mongoose": "^7.3.4", 15 | "zod": "^3.25.67" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /user-service/src/auth/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body } from '@nestjs/common'; 2 | import { AuthService } from './auth.service'; 3 | 4 | @Controller('auth') 5 | export class AuthController { 6 | constructor(private authService: AuthService) {} 7 | 8 | @Post('login') 9 | async login(@Body() body: { email: string, password: string }) { 10 | const user = await this.authService.validateUser(body.email, body.password); 11 | return this.authService.login(user); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /notification-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notification-service", 3 | "version": "1.0.0", 4 | "description": "* Listen to `order_created` or `order_confirmed` * Log or simulate email sending", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "type": "module", 13 | "dependencies": { 14 | "amqplib": "^0.10.3", 15 | "dotenv": "^16.3.1", 16 | "express": "^4.18.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /product-service/src/validators/product.validator.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | const productSchema = Joi.object({ 4 | name: Joi.string().min(2).max(100).required(), 5 | price: Joi.number().positive().required(), 6 | inStock: Joi.boolean().required(), 7 | }); 8 | 9 | const updateProductSchema = Joi.object({ 10 | name: Joi.string().min(2).max(100), 11 | price: Joi.number().positive(), 12 | inStock: Joi.boolean(), 13 | }).min(1); 14 | 15 | module.exports = { productSchema, updateProductSchema }; 16 | -------------------------------------------------------------------------------- /user-service/src/auth/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthService } from './auth.service'; 3 | 4 | describe('AuthService', () => { 5 | let service: AuthService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [AuthService], 10 | }).compile(); 11 | 12 | service = module.get(AuthService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /user-service/src/user/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserService } from './user.service'; 3 | 4 | describe('UserService', () => { 5 | let service: UserService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [UserService], 10 | }).compile(); 11 | 12 | service = module.get(UserService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /api-gateway/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { HttpModule } from '@nestjs/axios'; 3 | import { ProductsController } from './routes/products.controller'; 4 | import { OrdersController } from './routes/orders.controller'; 5 | import { UsersController } from './routes/users.controller'; 6 | import { ProxyService } from './proxy/proxy.service'; 7 | 8 | @Module({ 9 | imports: [HttpModule], 10 | controllers: [ProductsController, OrdersController, UsersController], 11 | providers: [ProxyService], 12 | }) 13 | export class AppModule {} -------------------------------------------------------------------------------- /api-gateway/src/proxy/proxy.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProxyService } from './proxy.service'; 3 | 4 | describe('ProxyService', () => { 5 | let service: ProxyService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ProxyService], 10 | }).compile(); 11 | 12 | service = module.get(ProxyService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /order-service/src/order/order.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OrderService } from './order.service'; 3 | 4 | describe('OrderService', () => { 5 | let service: OrderService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [OrderService], 10 | }).compile(); 11 | 12 | service = module.get(OrderService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /order-service/src/product/product.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Product } from './product.entity'; 4 | import { ProductService } from './product.service'; 5 | import { ProductController } from './product.controller'; 6 | 7 | @Module({ 8 | imports: [TypeOrmModule.forFeature([Product])], 9 | providers: [ 10 | //ProductService 11 | ], 12 | exports: [ 13 | //ProductService 14 | ], 15 | controllers: [ 16 | //ProductController 17 | ], 18 | }) 19 | export class ProductModule {} 20 | -------------------------------------------------------------------------------- /order-service/src/product/product.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProductService } from './product.service'; 3 | 4 | describe('ProductService', () => { 5 | let service: ProductService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [ProductService], 10 | }).compile(); 11 | 12 | service = module.get(ProductService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /user-service/src/auth/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { ExtractJwt, Strategy } from 'passport-jwt'; 4 | 5 | @Injectable() 6 | export class JwtStrategy extends PassportStrategy(Strategy) { 7 | constructor() { 8 | super({ 9 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 10 | secretOrKey: process.env.JWT_SECRET || 'supersecret', 11 | }); 12 | } 13 | 14 | async validate(payload: any) { 15 | return { userId: payload.sub, email: payload.email }; 16 | } 17 | } -------------------------------------------------------------------------------- /order-service/src/rabbitmq/rabbitmq.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { RabbitMQService } from './rabbitmq.service'; 3 | 4 | describe('RabbitService', () => { 5 | let service: RabbitMQService; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | providers: [RabbitMQService], 10 | }).compile(); 11 | 12 | service = module.get(RabbitMQService); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(service).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /product-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product-service", 3 | "version": "1.0.0", 4 | "description": "* Create / List / Delete products * Fetch product by ID * Store simple document schema", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "type": "commonjs", 13 | "dependencies": { 14 | "amqplib": "^0.10.8", 15 | "dotenv": "^17.2.0", 16 | "express": "^5.1.0", 17 | "joi": "^17.13.3", 18 | "mongoose": "^8.16.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /user-service/src/auth/auth.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AuthController } from './auth.controller'; 3 | 4 | describe('AuthController', () => { 5 | let controller: AuthController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [AuthController], 10 | }).compile(); 11 | 12 | controller = module.get(AuthController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /user-service/src/user/user.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { UserController } from './user.controller'; 3 | 4 | describe('UserController', () => { 5 | let controller: UserController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [UserController], 10 | }).compile(); 11 | 12 | controller = module.get(UserController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /order-service/src/order/order.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { OrderController } from './order.controller'; 3 | 4 | describe('OrderController', () => { 5 | let controller: OrderController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [OrderController], 10 | }).compile(); 11 | 12 | controller = module.get(OrderController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /order-service/src/product/product.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { ProductController } from './product.controller'; 3 | 4 | describe('ProductController', () => { 5 | let controller: ProductController; 6 | 7 | beforeEach(async () => { 8 | const module: TestingModule = await Test.createTestingModule({ 9 | controllers: [ProductController], 10 | }).compile(); 11 | 12 | controller = module.get(ProductController); 13 | }); 14 | 15 | it('should be defined', () => { 16 | expect(controller).toBeDefined(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /notification-service/src/queue/consumer.js: -------------------------------------------------------------------------------- 1 | import amqp from 'amqplib'; 2 | 3 | export async function startConsumer() { 4 | const conn = await amqp.connect(process.env.RABBITMQ_URL); 5 | const channel = await conn.createChannel(); 6 | 7 | const queue = 'order_rejected'; 8 | await channel.assertQueue(queue, { durable: false }); 9 | 10 | console.log(`📨 Listening for notifications on queue: ${queue}`); 11 | 12 | channel.consume(queue, (msg) => { 13 | if (!msg) return; 14 | const data = JSON.parse(msg.content.toString()); 15 | 16 | console.log(`❗ ORDER REJECTED ❗`, data); 17 | 18 | channel.ack(msg); 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /api-gateway/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2023", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noImplicitAny": false, 18 | "strictBindCallApply": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /order-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2023", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noImplicitAny": false, 18 | "strictBindCallApply": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /user-service/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true, 8 | "allowSyntheticDefaultImports": true, 9 | "target": "ES2023", 10 | "sourceMap": true, 11 | "outDir": "./dist", 12 | "baseUrl": "./", 13 | "incremental": true, 14 | "skipLibCheck": true, 15 | "strictNullChecks": true, 16 | "forceConsistentCasingInFileNames": true, 17 | "noImplicitAny": false, 18 | "strictBindCallApply": false, 19 | "noFallthroughCasesInSwitch": false 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /order-service/src/utils/rabbitmq.ts: -------------------------------------------------------------------------------- 1 | import * as amqp from 'amqplib'; 2 | 3 | let channel: amqp.Channel; 4 | 5 | export async function connectRabbitMQ() { 6 | const connection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://rabbitmq:5672'); 7 | channel = await connection.createChannel(); 8 | console.log('✅ order-service connected to RabbitMQ'); 9 | } 10 | 11 | export async function publishToQueue(queue: string, payload: any) { 12 | if (!channel) return; 13 | await channel.assertQueue(queue, { durable: false }); 14 | channel.sendToQueue(queue, Buffer.from(JSON.stringify(payload))); 15 | console.log(`📤 Published to ${queue}:`, payload); 16 | } 17 | -------------------------------------------------------------------------------- /order-service/src/order/order.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Order } from './order.entity'; 4 | import { OrderService } from './order.service'; 5 | import { OrderController } from './order.controller'; 6 | import { RabbitMQService } from 'src/rabbitmq/rabbitmq.service'; 7 | //import { ProductService } from 'src/product/product.service'; 8 | 9 | @Module({ 10 | imports: [TypeOrmModule.forFeature([Order])], 11 | providers: [ 12 | OrderService, 13 | RabbitMQService, 14 | //ProductService 15 | ], 16 | controllers: [OrderController], 17 | }) 18 | export class OrderModule {} 19 | -------------------------------------------------------------------------------- /product-service/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const express = require('express'); 3 | const mongoose = require('mongoose'); 4 | const productRoutes = require('./src/routes/product.routes'); 5 | const { connectRabbitMQ } = require('./src/utils/rabbitmq'); 6 | 7 | const app = express(); 8 | app.use(express.json()); 9 | 10 | app.use('/products', productRoutes); 11 | 12 | mongoose.connect(process.env.MONGODB_URL) 13 | .then(async () => { 14 | await connectRabbitMQ(); 15 | app.listen(process.env.PORT, () => console.log(`Product Service running on port ${process.env.PORT}`)); 16 | }) 17 | .catch(err => console.error('MongoDB connection error:', err)); 18 | -------------------------------------------------------------------------------- /order-service/src/product/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { Product } from './product.entity'; 5 | 6 | @Injectable() 7 | export class ProductService { 8 | constructor( 9 | @InjectRepository(Product) private productRepo: Repository 10 | ) {} 11 | 12 | async upsertProduct(data: Partial) { 13 | const product = this.productRepo.create(data); 14 | return this.productRepo.save(product); 15 | } 16 | 17 | async getById(id: string) { 18 | return this.productRepo.findOne({ where: { id } }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /user-service/src/main.ts: -------------------------------------------------------------------------------- 1 | import { NestFactory } from '@nestjs/core'; 2 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; 3 | import { AppModule } from './app.module'; 4 | import { ValidationPipe } from '@nestjs/common'; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | app.useGlobalPipes(new ValidationPipe()); 9 | 10 | const config = new DocumentBuilder() 11 | .setTitle('User Service') 12 | .setVersion('1.0') 13 | .build(); 14 | 15 | const document = SwaggerModule.createDocument(app, config); 16 | SwaggerModule.setup('docs', app, document); 17 | 18 | await app.listen(3010, '0.0.0.0'); 19 | } 20 | bootstrap(); 21 | -------------------------------------------------------------------------------- /user-service/src/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Post, Body, UseGuards, Get, Req, Request } from '@nestjs/common'; 2 | import { UserService } from './user.service'; 3 | import { CreateUserDto } from './dto/create-user.dto'; 4 | import { JwtAuthGuard } from 'src/auth/jwt-auth.guard'; 5 | 6 | @Controller('users') 7 | export class UserController { 8 | constructor(private readonly userService: UserService) {} 9 | 10 | @Post() 11 | async create(@Body() body: CreateUserDto) { 12 | return this.userService.createUser(body.email, body.password); 13 | } 14 | 15 | @UseGuards(JwtAuthGuard) 16 | @Get('me') 17 | async getProfile(@Request() req) { 18 | return req.user; 19 | } 20 | } -------------------------------------------------------------------------------- /product-service/src/utils/rabbitmq.js: -------------------------------------------------------------------------------- 1 | const amqp = require('amqplib'); 2 | 3 | let channel; 4 | 5 | async function connectRabbitMQ() { 6 | try { 7 | const connection = await amqp.connect(process.env.RABBITMQ_URL); 8 | channel = await connection.createChannel(); 9 | console.log('Connected to RabbitMQ'); 10 | } catch (error) { 11 | console.error('RabbitMQ connection error:', error); 12 | } 13 | } 14 | 15 | async function publishToQueue(queueName, message) { 16 | if (!channel) return; 17 | await channel.assertQueue(queueName, { durable: false }); 18 | channel.sendToQueue(queueName, Buffer.from(JSON.stringify(message))); 19 | } 20 | 21 | module.exports = { 22 | connectRabbitMQ, 23 | publishToQueue, 24 | }; 25 | -------------------------------------------------------------------------------- /api-gateway/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /api-gateway/src/proxy/proxy.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpService } from '@nestjs/axios'; 2 | import { Injectable } from '@nestjs/common'; 3 | import { firstValueFrom } from 'rxjs'; 4 | 5 | @Injectable() 6 | export class ProxyService { 7 | constructor(private http: HttpService) {} 8 | 9 | async forwardGet(url: string) { 10 | const res = await firstValueFrom(this.http.get(url)); 11 | return res.data; 12 | } 13 | 14 | async forwardPost(url: string, data: any) { 15 | const res = await firstValueFrom(this.http.post(url, data)); 16 | return res.data; 17 | } 18 | 19 | async forwardPatch(url: string, data: any) { 20 | const res = await firstValueFrom(this.http.patch(url, data)); 21 | return res.data; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /order-service/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /user-service/src/app.controller.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { AppController } from './app.controller'; 3 | import { AppService } from './app.service'; 4 | 5 | describe('AppController', () => { 6 | let appController: AppController; 7 | 8 | beforeEach(async () => { 9 | const app: TestingModule = await Test.createTestingModule({ 10 | controllers: [AppController], 11 | providers: [AppService], 12 | }).compile(); 13 | 14 | appController = app.get(AppController); 15 | }); 16 | 17 | describe('root', () => { 18 | it('should return "Hello World!"', () => { 19 | expect(appController.getHello()).toBe('Hello World!'); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /order-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { ConfigModule } from '@nestjs/config'; 4 | import { OrderModule } from './order/order.module'; 5 | import { RabbitMQService } from './rabbitmq/rabbitmq.service'; 6 | import { ProductModule } from './product/product.module'; 7 | 8 | @Module({ 9 | imports: [ 10 | ConfigModule.forRoot({ isGlobal: true }), 11 | TypeOrmModule.forRoot({ 12 | type: 'postgres', 13 | url: process.env.DATABASE_URL, 14 | autoLoadEntities: true, 15 | synchronize: true, 16 | }), 17 | //ProductModule, 18 | OrderModule, 19 | ], 20 | providers: [RabbitMQService], 21 | }) 22 | export class AppModule {} 23 | -------------------------------------------------------------------------------- /user-service/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { JwtModule } from '@nestjs/jwt'; 3 | import { PassportModule } from '@nestjs/passport'; 4 | 5 | import { AuthService } from './auth.service'; 6 | import { AuthController } from './auth.controller'; 7 | import { JwtStrategy } from './jwt.strategy'; 8 | import { UserModule } from '../user/user.module'; 9 | 10 | @Module({ 11 | imports: [ 12 | UserModule, 13 | PassportModule, 14 | JwtModule.register({ 15 | secret: process.env.JWT_SECRET || 'supersecret', 16 | signOptions: { expiresIn: '1d' }, 17 | }), 18 | ], 19 | controllers: [AuthController], 20 | providers: [AuthService, JwtStrategy], 21 | exports: [JwtModule] 22 | }) 23 | export class AuthModule {} 24 | -------------------------------------------------------------------------------- /api-gateway/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { App } from 'supertest/types'; 5 | import { AppModule } from './../src/app.module'; 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication; 9 | 10 | beforeEach(async () => { 11 | const moduleFixture: TestingModule = await Test.createTestingModule({ 12 | imports: [AppModule], 13 | }).compile(); 14 | 15 | app = moduleFixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | it('/ (GET)', () => { 20 | return request(app.getHttpServer()) 21 | .get('/') 22 | .expect(200) 23 | .expect('Hello World!'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /user-service/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { App } from 'supertest/types'; 5 | import { AppModule } from './../src/app.module'; 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication; 9 | 10 | beforeEach(async () => { 11 | const moduleFixture: TestingModule = await Test.createTestingModule({ 12 | imports: [AppModule], 13 | }).compile(); 14 | 15 | app = moduleFixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | it('/ (GET)', () => { 20 | return request(app.getHttpServer()) 21 | .get('/') 22 | .expect(200) 23 | .expect('Hello World!'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /order-service/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { INestApplication } from '@nestjs/common'; 3 | import * as request from 'supertest'; 4 | import { App } from 'supertest/types'; 5 | import { AppModule } from './../src/app.module'; 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication; 9 | 10 | beforeEach(async () => { 11 | const moduleFixture: TestingModule = await Test.createTestingModule({ 12 | imports: [AppModule], 13 | }).compile(); 14 | 15 | app = moduleFixture.createNestApplication(); 16 | await app.init(); 17 | }); 18 | 19 | it('/ (GET)', () => { 20 | return request(app.getHttpServer()) 21 | .get('/') 22 | .expect(200) 23 | .expect('Hello World!'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /user-service/src/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { User } from './user.entity'; 5 | import * as bcrypt from 'bcrypt'; 6 | 7 | @Injectable() 8 | export class UserService { 9 | constructor( 10 | @InjectRepository(User) private readonly userRepo: Repository, 11 | ) {} 12 | 13 | async createUser(email: string, password: string) { 14 | const hashed = await bcrypt.hash(password, 10); 15 | const user = this.userRepo.create({ email, password: hashed }); 16 | return this.userRepo.save(user); 17 | } 18 | 19 | async findByEmail(email: string): Promise { 20 | const user = await this.userRepo.findOne({ where: { email } }); 21 | return user ?? undefined; 22 | } 23 | } -------------------------------------------------------------------------------- /user-service/src/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, UnauthorizedException } from '@nestjs/common'; 2 | import { JwtService } from '@nestjs/jwt'; 3 | import * as bcrypt from 'bcrypt'; 4 | import { UserService } from '../user/user.service'; 5 | 6 | @Injectable() 7 | export class AuthService { 8 | constructor(private jwtService: JwtService, private userService: UserService) {} 9 | 10 | async validateUser(email: string, password: string) { 11 | const user = await this.userService.findByEmail(email); 12 | if (user && await bcrypt.compare(password, user.password)) { 13 | const { password, ...result } = user; 14 | return result; 15 | } 16 | throw new UnauthorizedException('Invalid credentials'); 17 | } 18 | 19 | async login(user: any) { 20 | const payload = { sub: user.id, email: user.email }; 21 | return { 22 | access_token: this.jwtService.sign(payload), 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api-gateway/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /order-event-listener/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const amqp = require('amqplib'); 3 | 4 | const QUEUES = ['order_created', 'order_updated']; 5 | 6 | async function start() { 7 | try { 8 | const connection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://rabbitmq:5672'); 9 | const channel = await connection.createChannel(); 10 | 11 | for (const queue of QUEUES) { 12 | await channel.assertQueue(queue, { durable: false }); 13 | channel.consume(queue, msg => { 14 | if (msg !== null) { 15 | const content = msg.content.toString(); 16 | console.log(`📦 [${queue.toUpperCase()}] Event received:`, JSON.parse(content)); 17 | channel.ack(msg); 18 | } 19 | }); 20 | } 21 | 22 | console.log(`🟢 Listening for order events: ${QUEUES.join(', ')}`); 23 | } catch (err) { 24 | console.error('❌ Failed to start order-event-listener:', err); 25 | } 26 | } 27 | start(); 28 | -------------------------------------------------------------------------------- /order-service/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /user-service/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | 18 | # Tests 19 | /coverage 20 | /.nyc_output 21 | 22 | # IDEs and editors 23 | /.idea 24 | .project 25 | .classpath 26 | .c9/ 27 | *.launch 28 | .settings/ 29 | *.sublime-workspace 30 | 31 | # IDE - VSCode 32 | .vscode/* 33 | !.vscode/settings.json 34 | !.vscode/tasks.json 35 | !.vscode/launch.json 36 | !.vscode/extensions.json 37 | 38 | # dotenv environment variable files 39 | .env 40 | .env.development.local 41 | .env.test.local 42 | .env.production.local 43 | .env.local 44 | 45 | # temp directory 46 | .temp 47 | .tmp 48 | 49 | # Runtime data 50 | pids 51 | *.pid 52 | *.seed 53 | *.pid.lock 54 | 55 | # Diagnostic reports (https://nodejs.org/api/report.html) 56 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 57 | -------------------------------------------------------------------------------- /user-service/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { ConfigModule, ConfigService } from '@nestjs/config'; 4 | import { UserModule } from './user/user.module'; 5 | import { AuthModule } from './auth/auth.module'; 6 | import { AuthService } from './auth/auth.service'; 7 | import { JwtStrategy } from './auth/jwt.strategy'; 8 | 9 | @Module({ 10 | imports: [ 11 | ConfigModule.forRoot({ isGlobal: true }), 12 | TypeOrmModule.forRootAsync({ 13 | imports: [ConfigModule], 14 | useFactory: (config: ConfigService) => ({ 15 | type: 'postgres', 16 | url: config.get('DATABASE_URL'), 17 | autoLoadEntities: true, 18 | synchronize: true, 19 | }), 20 | inject: [ConfigService], 21 | }), 22 | AuthModule, 23 | UserModule, 24 | ], 25 | providers: [AuthService, JwtStrategy], 26 | }) 27 | export class AppModule {} 28 | -------------------------------------------------------------------------------- /api-gateway/src/proxy/jwt-auth.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; 2 | import * as jwt from 'jsonwebtoken'; 3 | 4 | @Injectable() 5 | export class JwtAuthGuard implements CanActivate { 6 | async canActivate(context: ExecutionContext): Promise { 7 | const request = context.switchToHttp().getRequest(); 8 | const authHeader = request.headers['authorization']; 9 | if (!authHeader) throw new UnauthorizedException('No token provided'); 10 | 11 | const token = authHeader.split(' ')[1]; 12 | if (!token) throw new UnauthorizedException('Invalid token format'); 13 | 14 | try { 15 | // Use the same secret as your user-service 16 | const decoded = jwt.verify(token, process.env.JWT_SECRET || 'your_jwt_secret'); 17 | request.user = decoded; 18 | return true; 19 | } catch (err) { 20 | throw new UnauthorizedException('Invalid or expired token'); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /api-gateway/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import eslint from '@eslint/js'; 3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 4 | import globals from 'globals'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { 9 | ignores: ['eslint.config.mjs'], 10 | }, 11 | eslint.configs.recommended, 12 | ...tseslint.configs.recommendedTypeChecked, 13 | eslintPluginPrettierRecommended, 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.node, 18 | ...globals.jest, 19 | }, 20 | sourceType: 'commonjs', 21 | parserOptions: { 22 | projectService: true, 23 | tsconfigRootDir: import.meta.dirname, 24 | }, 25 | }, 26 | }, 27 | { 28 | rules: { 29 | '@typescript-eslint/no-explicit-any': 'off', 30 | '@typescript-eslint/no-floating-promises': 'warn', 31 | '@typescript-eslint/no-unsafe-argument': 'warn' 32 | }, 33 | }, 34 | ); -------------------------------------------------------------------------------- /order-service/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import eslint from '@eslint/js'; 3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 4 | import globals from 'globals'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { 9 | ignores: ['eslint.config.mjs'], 10 | }, 11 | eslint.configs.recommended, 12 | ...tseslint.configs.recommendedTypeChecked, 13 | eslintPluginPrettierRecommended, 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.node, 18 | ...globals.jest, 19 | }, 20 | sourceType: 'commonjs', 21 | parserOptions: { 22 | projectService: true, 23 | tsconfigRootDir: import.meta.dirname, 24 | }, 25 | }, 26 | }, 27 | { 28 | rules: { 29 | '@typescript-eslint/no-explicit-any': 'off', 30 | '@typescript-eslint/no-floating-promises': 'warn', 31 | '@typescript-eslint/no-unsafe-argument': 'warn' 32 | }, 33 | }, 34 | ); -------------------------------------------------------------------------------- /user-service/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import eslint from '@eslint/js'; 3 | import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; 4 | import globals from 'globals'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { 9 | ignores: ['eslint.config.mjs'], 10 | }, 11 | eslint.configs.recommended, 12 | ...tseslint.configs.recommendedTypeChecked, 13 | eslintPluginPrettierRecommended, 14 | { 15 | languageOptions: { 16 | globals: { 17 | ...globals.node, 18 | ...globals.jest, 19 | }, 20 | sourceType: 'commonjs', 21 | parserOptions: { 22 | projectService: true, 23 | tsconfigRootDir: import.meta.dirname, 24 | }, 25 | }, 26 | }, 27 | { 28 | rules: { 29 | '@typescript-eslint/no-explicit-any': 'off', 30 | '@typescript-eslint/no-floating-promises': 'warn', 31 | '@typescript-eslint/no-unsafe-argument': 'warn' 32 | }, 33 | }, 34 | ); -------------------------------------------------------------------------------- /nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | upstream user_service { 2 | server 172.21.0.6:3010; 3 | } 4 | upstream product_service { 5 | server 172.21.0.7:3004; 6 | } 7 | upstream order_service { 8 | server 172.21.0.8:3009; 9 | } 10 | upstream inventory_service { 11 | server 172.21.0.9:3007; 12 | } 13 | upstream notification_service { 14 | server 172.21.0.10:3005; 15 | } 16 | upstream api_gateway { 17 | server 172.21.0.11:3011; 18 | } 19 | 20 | 21 | server { 22 | listen 80; 23 | 24 | location / { 25 | proxy_pass http://api_gateway; 26 | } 27 | location /users/ { 28 | proxy_pass http://api_gateway/users/; 29 | } 30 | 31 | location /products/ { 32 | proxy_pass http://api_gateway/products/; 33 | } 34 | 35 | location /orders/ { 36 | proxy_pass http://api_gateway/orders/; 37 | } 38 | 39 | location /api/ { 40 | proxy_pass http://api_gateway/api/; 41 | } 42 | 43 | location /docs/ { 44 | proxy_pass http://api_gateway/docs/; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /product-event-listener/index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const amqp = require('amqplib'); 3 | 4 | const RABBITMQ_URL = process.env.RABBITMQ_URL; 5 | 6 | async function start() { 7 | try { 8 | const connection = await amqp.connect(RABBITMQ_URL); 9 | const channel = await connection.createChannel(); 10 | 11 | await channel.assertQueue('product_created', { durable: false }); 12 | await channel.assertQueue('product_updated', { durable: false }); 13 | 14 | console.log('🟢 Waiting for product events...'); 15 | 16 | channel.consume('product_created', msg => { 17 | const content = msg.content.toString(); 18 | console.log('📦 [CREATE] product_created event:', JSON.parse(content)); 19 | channel.ack(msg); 20 | }); 21 | 22 | channel.consume('product_updated', msg => { 23 | const content = msg.content.toString(); 24 | console.log('🛠️ [UPDATE] product_updated event:', JSON.parse(content)); 25 | channel.ack(msg); 26 | }); 27 | } catch (err) { 28 | console.error('❌ RabbitMQ error:', err); 29 | } 30 | } 31 | 32 | start(); 33 | -------------------------------------------------------------------------------- /api-gateway/src/routes/users.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Param, Patch } from '@nestjs/common'; 2 | import { ProxyService } from '../proxy/proxy.service'; 3 | import * as dotenv from 'dotenv'; 4 | dotenv.config(); 5 | 6 | @Controller('users') 7 | export class UsersController { 8 | constructor(private proxy: ProxyService) {} 9 | 10 | @Post('login') 11 | login(@Body() body: any) { 12 | return this.proxy.forwardPost(`${process.env.USER_SERVICE_URL}/auth/login`, body); 13 | } 14 | 15 | @Post('register') 16 | register(@Body() body: any) { 17 | return this.proxy.forwardPost(`${process.env.USER_SERVICE_URL}/users`, body); 18 | } 19 | 20 | @Get('/me') 21 | getUser() { 22 | return this.proxy.forwardGet(`${process.env.USER_SERVICE_URL}/users/me`); 23 | } 24 | 25 | @Get(':id') 26 | getById(@Param('id') id: string) { 27 | return this.proxy.forwardGet(`${process.env.USER_SERVICE_URL}/users/${id}`); 28 | } 29 | 30 | @Patch(':id') 31 | update(@Param('id') id: string, @Body() body: any) { 32 | return this.proxy.forwardPatch(`${process.env.USER_SERVICE_URL}/users/${id}`, body); 33 | } 34 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-microservices", 3 | "version": "1.0.0", 4 | "description": "![Repository Size](https://img.shields.io/github/repo-size/JawherKl/nodejs-microservices) ![Last Commit](https://img.shields.io/github/last-commit/JawherKl/nodejs-microservices) ![Issues](https://img.shields.io/github/issues-raw/JawherKl/nodejs-microservices) ![Forks](https://img.shields.io/github/forks/JawherKl/nodejs-microservices) ![Stars](https://img.shields.io/github/stars/JawherKl/nodejs-microservices)", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/JawherKl/nodejs-microservices.git" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "type": "commonjs", 17 | "bugs": { 18 | "url": "https://github.com/JawherKl/nodejs-microservices/issues" 19 | }, 20 | "homepage": "https://github.com/JawherKl/nodejs-microservices#readme", 21 | "dependencies": { 22 | "dotenv": "^16.5.0", 23 | "express": "^5.1.0", 24 | "mongoose": "^8.16.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /api-gateway/src/routes/orders.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Param, Patch, Delete } from '@nestjs/common'; 2 | import { ProxyService } from '../proxy/proxy.service'; 3 | import * as dotenv from 'dotenv'; 4 | dotenv.config(); 5 | 6 | @Controller('orders') 7 | export class OrdersController { 8 | constructor(private proxy: ProxyService) {} 9 | 10 | @Get() 11 | getAll() { 12 | return this.proxy.forwardGet(`${process.env.ORDER_SERVICE_URL}/orders`); 13 | } 14 | 15 | @Post() 16 | create(@Body() body: any) { 17 | return this.proxy.forwardPost(`${process.env.ORDER_SERVICE_URL}/orders`, body); 18 | } 19 | 20 | @Get(':id') 21 | getById(@Param('id') id: string) { 22 | return this.proxy.forwardGet(`${process.env.ORDER_SERVICE_URL}/orders/${id}`); 23 | } 24 | 25 | @Patch(':id') 26 | update(@Param('id') id: string, @Body() body: any) { 27 | return this.proxy.forwardPatch(`${process.env.ORDER_SERVICE_URL}/orders/${id}`, body); 28 | } 29 | 30 | @Delete(':id') 31 | remove(@Param('id') id: string) { 32 | return this.proxy.forwardPatch(`${process.env.ORDER_SERVICE_URL}/orders/${id}`, {}); 33 | } 34 | } -------------------------------------------------------------------------------- /api-gateway/src/routes/products.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Post, Body, Param, Patch } from '@nestjs/common'; 2 | import { ProxyService } from '../proxy/proxy.service'; 3 | import * as dotenv from 'dotenv'; 4 | dotenv.config(); 5 | 6 | @Controller('products') 7 | export class ProductsController { 8 | constructor(private proxy: ProxyService) {} 9 | 10 | @Get() 11 | getAll() { 12 | return this.proxy.forwardGet(`${process.env.PRODUCT_SERVICE_URL}/products`); 13 | } 14 | 15 | @Post() 16 | create(@Body() body: any) { 17 | return this.proxy.forwardPost(`${process.env.PRODUCT_SERVICE_URL}/products`, body); 18 | } 19 | 20 | @Get(':id') 21 | getById(@Param('id') id: string) { 22 | return this.proxy.forwardGet(`${process.env.PRODUCT_SERVICE_URL}/products/${id}`); 23 | } 24 | 25 | @Get(':id/stock') 26 | getStock(@Param('id') id: string) { 27 | return this.proxy.forwardGet(`${process.env.INVENTORY_SERVICE_URL}/products/${id}/stock`); 28 | } 29 | 30 | @Patch(':id/replenish') 31 | replenish(@Param('id') id: string, @Body() body: any) { 32 | return this.proxy.forwardPatch(`${process.env.INVENTORY_SERVICE_URL}/products/${id}/replenish`, body); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /order-service/src/order/order.controller.ts: -------------------------------------------------------------------------------- 1 | import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common'; 2 | import { OrderService } from './order.service'; 3 | import { IsUUID, IsInt, Min } from 'class-validator'; 4 | 5 | class CreateOrderDto { 6 | @IsUUID() 7 | productId: string; 8 | 9 | @IsUUID() 10 | userId: string; 11 | 12 | @IsInt() 13 | @Min(1) 14 | quantity: number; 15 | } 16 | 17 | class UpdateOrderDto { 18 | @IsInt() 19 | @Min(1) 20 | quantity: number; 21 | } 22 | 23 | @Controller('orders') 24 | export class OrderController { 25 | constructor(private orderService: OrderService) {} 26 | 27 | @Post() 28 | create(@Body() body: CreateOrderDto) { 29 | return this.orderService.createOrder(body); 30 | } 31 | 32 | @Get() 33 | findAll() { 34 | return this.orderService.getAll(); 35 | } 36 | 37 | @Get(':id') 38 | findOne(@Param('id') id: string) { 39 | return this.orderService.getById(id); 40 | } 41 | 42 | @Patch(':id') 43 | update(@Param('id') id: string, @Body() body: UpdateOrderDto) { 44 | return this.orderService.updateOrder(id, body); 45 | } 46 | 47 | @Delete(':id') 48 | remove(@Param('id') id: string) { 49 | return this.orderService.deleteOrder(id); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /start-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(dirname "$0")" 4 | 5 | mkdir -p logs 6 | touch logs/product-service.log logs/product-event-listener.log logs/order-service.log logs/user-service.log logs/inventory-service.log logs/api-gateway.log logs/notification-service.log 7 | 8 | echo "Starting Docker containers..." 9 | docker-compose up -d 10 | 11 | # Start all Node/NestJS services in the background and log output 12 | echo "Starting product-service..." 13 | (cd product-service && npm run start > ../logs/product-service.log 2>&1 &) 14 | 15 | echo "Starting product-event-listener..." 16 | (cd product-event-listener && node index.js > ../logs/product-event-listener.log 2>&1 &) 17 | 18 | echo "Starting order-service..." 19 | (cd order-service && npm run start > ../logs/order-service.log 2>&1 &) 20 | 21 | echo "Starting user-service..." 22 | (cd user-service && npm run start > ../logs/user-service.log 2>&1 &) 23 | 24 | echo "Starting inventory-service..." 25 | (cd inventory-service && npm run start > ../logs/inventory-service.log 2>&1 &) 26 | 27 | echo "Starting api-gateway..." 28 | (cd api-gateway && npm run start > ../logs/api-gateway.log 2>&1 &) 29 | 30 | echo "Starting notification-service..." 31 | (cd notification-service && npm run start > ../logs/notification-service.log 2>&1 &) 32 | 33 | echo "All services started. Tailing logs..." 34 | tail -f logs/*.log -------------------------------------------------------------------------------- /order-service/src/order/order.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { Order } from './order.entity'; 5 | import { publishToQueue } from 'src/utils/rabbitmq'; 6 | 7 | @Injectable() 8 | export class OrderService { 9 | constructor( 10 | @InjectRepository(Order) private repo: Repository 11 | ) {} 12 | 13 | async createOrder(data: Partial) { 14 | const order = this.repo.create(data); 15 | const saved = await this.repo.save(order); 16 | await publishToQueue('order_created', saved); 17 | return saved; 18 | } 19 | 20 | 21 | async updateOrder(id: string, updates: Partial) { 22 | await this.repo.update(id, updates); 23 | const updated = await this.repo.findOne({ where: { id } }); 24 | if (updated) { 25 | await publishToQueue('order_updated', updated); 26 | } 27 | return updated; 28 | } 29 | 30 | async getAll() { 31 | return this.repo.find(); 32 | } 33 | async getById(id: string) { 34 | return this.repo.findOne({ where: { id } }); 35 | } 36 | 37 | async deleteOrder(id: string) { 38 | const order = await this.repo.findOne({ where: { id } }); 39 | if (order) { 40 | await this.repo.delete(id); 41 | // Optionally publish to queue: 42 | // await publishToQueue('order_deleted', { id }); 43 | } 44 | return order; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /order-service/src/rabbitmq/rabbitmq.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnModuleInit } from '@nestjs/common'; 2 | import * as amqp from 'amqplib'; 3 | import { ProductService } from '../product/product.service'; 4 | import { Inject } from '@nestjs/common'; 5 | 6 | @Injectable() 7 | export class RabbitMQService implements OnModuleInit { 8 | private connection: amqp.Connection; 9 | private channel: amqp.Channel; 10 | private readonly queue = 'product_created'; 11 | 12 | //constructor(private readonly productService: ProductService) {} 13 | 14 | async onModuleInit() { 15 | await this.connect(); 16 | //await this.consume(); 17 | } 18 | 19 | private async connect() { 20 | try { 21 | this.connection = await amqp.connect(process.env.RABBITMQ_URL || 'amqp://rabbitmq:5672'); 22 | this.channel = await this.connection.createChannel(); 23 | console.log('✅ Connected to RabbitMQ (order-service)'); 24 | } catch (err) { 25 | console.error('❌ RabbitMQ connection failed:', err); 26 | } 27 | } 28 | 29 | /*private async consume() { 30 | if (!this.channel) return; 31 | 32 | await this.channel.assertQueue(this.queue, { durable: false }); 33 | 34 | this.channel.consume(this.queue, (msg) => { 35 | if (msg !== null) { 36 | const content = msg.content.toString(); 37 | const product = JSON.parse(content); 38 | 39 | console.log('📥 [ORDER-SERVICE] Received product_created:', product); 40 | 41 | this.productService.upsertProduct(product); 42 | 43 | this.channel.ack(msg); 44 | } 45 | }); 46 | }*/ 47 | } 48 | -------------------------------------------------------------------------------- /inventory-service/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import dotenv from 'dotenv'; 3 | import { connectDB } from './src/db.js'; 4 | import { startConsumer } from './src/queue/consumer.js'; 5 | import { Product } from './src/models/product.js'; 6 | import { z } from 'zod'; 7 | 8 | dotenv.config(); 9 | const app = express(); 10 | app.use(express.json()); 11 | 12 | const productSchema = z.object({ 13 | _id: z.string(), 14 | name: z.string(), 15 | price: z.number().min(0), 16 | stock: z.number().int().min(0), 17 | }); 18 | 19 | app.use(express.json()); 20 | 21 | // Health check 22 | app.get('/', (req, res) => { 23 | res.send('🛒 Inventory Service is running'); 24 | }); 25 | 26 | app.post('/products', async (req, res) => { 27 | const { _id, name, price, stock } = req.body; 28 | if (!_id || !name || typeof price !== 'number' || typeof stock !== 'number') { 29 | return res.status(400).json({ message: 'Invalid product data' }); 30 | } 31 | const product = new Product({ _id, name, price, stock }); 32 | await product.save(); 33 | res.status(201).json(product); 34 | }); 35 | 36 | app.get('/products/:id/stock', async (req, res) => { 37 | const product = await Product.findById(req.params.id); 38 | if (!product) return res.status(404).json({ message: 'Product not found' }); 39 | res.json({ productId: product._id, stock: product.stock }); 40 | }); 41 | 42 | app.patch('/products/:id/replenish', async (req, res) => { 43 | const { quantity } = req.body; 44 | if (!Number.isInteger(quantity) || quantity <= 0) { 45 | return res.status(400).json({ message: 'Quantity must be a positive integer' }); 46 | } 47 | const product = await Product.findById(req.params.id); 48 | if (!product) return res.status(404).json({ message: 'Product not found' }); 49 | product.stock += quantity; 50 | await product.save(); 51 | res.json({ message: 'Stock replenished', productId: product._id, newStock: product.stock }); 52 | }); 53 | 54 | const PORT = process.env.PORT || 3007; 55 | app.listen(PORT, async () => { 56 | console.log(`🚀 Inventory service running on port ${PORT}`); 57 | await connectDB(); 58 | await startConsumer(); 59 | }); -------------------------------------------------------------------------------- /inventory-service/src/queue/consumer.js: -------------------------------------------------------------------------------- 1 | import amqp from 'amqplib'; 2 | import { Product } from '../models/product.js'; 3 | 4 | let channel; 5 | 6 | async function publishToQueue(queue, data) { 7 | await channel.assertQueue(queue, { durable: false }); 8 | channel.sendToQueue(queue, Buffer.from(JSON.stringify(data))); 9 | console.log(`📤 Published to ${queue}`, data); 10 | } 11 | 12 | export async function startConsumer() { 13 | const conn = await amqp.connect(process.env.RABBITMQ_URL); 14 | channel = await conn.createChannel(); 15 | 16 | await channel.assertQueue('product_created', { durable: false }); 17 | channel.consume('product_created', async (msg) => { 18 | if (!msg) return; 19 | const data = JSON.parse(msg.content.toString()); 20 | let product = await Product.findById(data.id); 21 | if (!product) { 22 | product = new Product({ 23 | _id: data.id, 24 | name: data.name, 25 | price: data.price, 26 | stock: typeof data.stock === 'number' ? data.stock : 0, 27 | }); 28 | await product.save(); 29 | console.log('✅ Product created in inventory:', product); 30 | } 31 | channel.ack(msg); 32 | }); 33 | 34 | const queue = 'order_created'; 35 | await channel.assertQueue(queue, { durable: false }); 36 | 37 | console.log(`🎧 Listening to queue: ${queue}`); 38 | 39 | channel.consume(queue, async (msg) => { 40 | if (!msg) return; 41 | 42 | const order = JSON.parse(msg.content.toString()); 43 | console.log('📥 Received order_created:', order); 44 | 45 | const product = await Product.findById(order.productId); 46 | 47 | if (!product) { 48 | console.log('❌ Product not found'); 49 | await publishToQueue('order_rejected', { 50 | orderId: order.id, 51 | reason: 'Product not found', 52 | }); 53 | return channel.ack(msg); 54 | } 55 | 56 | if (product.stock < order.quantity) { 57 | console.log('❌ Insufficient stock'); 58 | await publishToQueue('order_rejected', { 59 | orderId: order.id, 60 | reason: 'Insufficient stock', 61 | }); 62 | } else { 63 | product.stock -= order.quantity; 64 | await product.save(); 65 | console.log(`✅ Stock updated for ${product._id}`); 66 | 67 | await publishToQueue('order_accepted', { 68 | orderId: order.id, 69 | productId: product._id, 70 | quantity: order.quantity, 71 | }); 72 | } 73 | 74 | channel.ack(msg); 75 | }); 76 | } -------------------------------------------------------------------------------- /api-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api-gateway", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "node dist/main", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/axios": "^4.0.0", 24 | "@nestjs/common": "^11.0.1", 25 | "@nestjs/core": "^11.0.1", 26 | "@nestjs/passport": "^11.0.5", 27 | "@nestjs/platform-express": "^11.0.1", 28 | "dotenv": "^17.0.1", 29 | "passport": "^0.7.0", 30 | "passport-jwt": "^4.0.1", 31 | "reflect-metadata": "^0.2.2", 32 | "rxjs": "^7.8.1" 33 | }, 34 | "devDependencies": { 35 | "@eslint/eslintrc": "^3.2.0", 36 | "@eslint/js": "^9.18.0", 37 | "@nestjs/cli": "^11.0.0", 38 | "@nestjs/schematics": "^11.0.0", 39 | "@nestjs/testing": "^11.0.1", 40 | "@swc/cli": "^0.6.0", 41 | "@swc/core": "^1.10.7", 42 | "@types/express": "^5.0.0", 43 | "@types/jest": "^29.5.14", 44 | "@types/node": "^22.10.7", 45 | "@types/supertest": "^6.0.2", 46 | "eslint": "^9.18.0", 47 | "eslint-config-prettier": "^10.0.1", 48 | "eslint-plugin-prettier": "^5.2.2", 49 | "globals": "^16.0.0", 50 | "jest": "^29.7.0", 51 | "prettier": "^3.4.2", 52 | "source-map-support": "^0.5.21", 53 | "supertest": "^7.0.0", 54 | "ts-jest": "^29.2.5", 55 | "ts-loader": "^9.5.2", 56 | "ts-node": "^10.9.2", 57 | "tsconfig-paths": "^4.2.0", 58 | "typescript": "^5.7.3", 59 | "typescript-eslint": "^8.20.0" 60 | }, 61 | "jest": { 62 | "moduleFileExtensions": [ 63 | "js", 64 | "json", 65 | "ts" 66 | ], 67 | "rootDir": "src", 68 | "testRegex": ".*\\.spec\\.ts$", 69 | "transform": { 70 | "^.+\\.(t|j)s$": "ts-jest" 71 | }, 72 | "collectCoverageFrom": [ 73 | "**/*.(t|j)s" 74 | ], 75 | "coverageDirectory": "../coverage", 76 | "testEnvironment": "node" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /product-service/src/controllers/product.controller.js: -------------------------------------------------------------------------------- 1 | const Product = require('../models/product.model'); 2 | const { productSchema, updateProductSchema } = require('../validators/product.validator'); 3 | const { publishToQueue } = require('../utils/rabbitmq'); 4 | 5 | exports.getAll = async (req, res) => { 6 | const products = await Product.find(); 7 | res.json(products); 8 | }; 9 | 10 | exports.create = async (req, res) => { 11 | try { 12 | const { error, value } = productSchema.validate(req.body); 13 | if (error) { 14 | return res.status(400).json({ message: error.details[0].message }); 15 | } 16 | 17 | const product = new Product(value); 18 | await product.save(); 19 | 20 | await publishToQueue('product_created', { 21 | id: product._id, 22 | name: product.name, 23 | price: product.price, 24 | inStock: product.inStock, 25 | }); 26 | 27 | res.status(201).json(product); 28 | } catch (err) { 29 | res.status(500).json({ message: err.message }); 30 | } 31 | }; 32 | 33 | exports.update = async (req, res) => { 34 | try { 35 | const { error, value } = updateProductSchema.validate(req.body); 36 | if (error) return res.status(400).json({ message: error.details[0].message }); 37 | 38 | const product = await Product.findByIdAndUpdate(req.params.id, value, { new: true }); 39 | 40 | if (!product) return res.status(404).json({ message: 'Product not found' }); 41 | 42 | await publishToQueue('product_updated', { 43 | id: product._id, 44 | name: product.name, 45 | price: product.price, 46 | inStock: product.inStock, 47 | }); 48 | 49 | res.json(product); 50 | } catch (err) { 51 | res.status(500).json({ message: err.message }); 52 | } 53 | }; 54 | 55 | exports.getById = async (req, res) => { 56 | try { 57 | const product = await Product.findById(req.params.id); 58 | if (!product) return res.status(404).json({ message: 'Product not found' }); 59 | res.json(product); 60 | } catch (err) { 61 | res.status(500).json({ message: err.message }); 62 | } 63 | }; 64 | 65 | exports.delete = async (req, res) => { 66 | try { 67 | const product = await Product.findByIdAndDelete(req.params.id); 68 | if (!product) return res.status(404).json({ message: 'Product not found' }); 69 | 70 | await publishToQueue('product_deleted', { id: product._id }); 71 | 72 | res.json({ message: 'Product deleted' }); 73 | } catch (err) { 74 | res.status(500).json({ message: err.message }); 75 | } 76 | } -------------------------------------------------------------------------------- /order-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order-service", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "node dist/main", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^11.0.1", 24 | "@nestjs/config": "^4.0.2", 25 | "@nestjs/core": "^11.0.1", 26 | "@nestjs/platform-express": "^11.0.1", 27 | "@nestjs/typeorm": "^11.0.0", 28 | "amqplib": "^0.10.8", 29 | "class-transformer": "^0.5.1", 30 | "class-validator": "^0.14.2", 31 | "pg": "^8.16.2", 32 | "reflect-metadata": "^0.2.2", 33 | "rxjs": "^7.8.1", 34 | "typeorm": "^0.3.25" 35 | }, 36 | "devDependencies": { 37 | "@eslint/eslintrc": "^3.2.0", 38 | "@eslint/js": "^9.18.0", 39 | "@nestjs/cli": "^11.0.0", 40 | "@nestjs/schematics": "^11.0.0", 41 | "@nestjs/testing": "^11.0.1", 42 | "@swc/cli": "^0.6.0", 43 | "@swc/core": "^1.10.7", 44 | "@types/express": "^5.0.0", 45 | "@types/jest": "^29.5.14", 46 | "@types/node": "^22.10.7", 47 | "@types/supertest": "^6.0.2", 48 | "eslint": "^9.18.0", 49 | "eslint-config-prettier": "^10.0.1", 50 | "eslint-plugin-prettier": "^5.2.2", 51 | "globals": "^16.0.0", 52 | "jest": "^29.7.0", 53 | "prettier": "^3.4.2", 54 | "source-map-support": "^0.5.21", 55 | "supertest": "^7.0.0", 56 | "ts-jest": "^29.2.5", 57 | "ts-loader": "^9.5.2", 58 | "ts-node": "^10.9.2", 59 | "tsconfig-paths": "^4.2.0", 60 | "typescript": "^5.7.3", 61 | "typescript-eslint": "^8.20.0" 62 | }, 63 | "jest": { 64 | "moduleFileExtensions": [ 65 | "js", 66 | "json", 67 | "ts" 68 | ], 69 | "rootDir": "src", 70 | "testRegex": ".*\\.spec\\.ts$", 71 | "transform": { 72 | "^.+\\.(t|j)s$": "ts-jest" 73 | }, 74 | "collectCoverageFrom": [ 75 | "**/*.(t|j)s" 76 | ], 77 | "coverageDirectory": "../coverage", 78 | "testEnvironment": "node" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /order-event-listener/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "order-event-listener", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "order-event-listener", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "amqplib": "^0.10.8", 13 | "dotenv": "^16.5.0" 14 | } 15 | }, 16 | "node_modules/amqplib": { 17 | "version": "0.10.8", 18 | "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.8.tgz", 19 | "integrity": "sha512-Tfn1O9sFgAP8DqeMEpt2IacsVTENBpblB3SqLdn0jK2AeX8iyCvbptBc8lyATT9bQ31MsjVwUSQ1g8f4jHOUfw==", 20 | "license": "MIT", 21 | "dependencies": { 22 | "buffer-more-ints": "~1.0.0", 23 | "url-parse": "~1.5.10" 24 | }, 25 | "engines": { 26 | "node": ">=10" 27 | } 28 | }, 29 | "node_modules/buffer-more-ints": { 30 | "version": "1.0.0", 31 | "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", 32 | "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", 33 | "license": "MIT" 34 | }, 35 | "node_modules/dotenv": { 36 | "version": "16.5.0", 37 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", 38 | "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", 39 | "license": "BSD-2-Clause", 40 | "engines": { 41 | "node": ">=12" 42 | }, 43 | "funding": { 44 | "url": "https://dotenvx.com" 45 | } 46 | }, 47 | "node_modules/querystringify": { 48 | "version": "2.2.0", 49 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 50 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", 51 | "license": "MIT" 52 | }, 53 | "node_modules/requires-port": { 54 | "version": "1.0.0", 55 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 56 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", 57 | "license": "MIT" 58 | }, 59 | "node_modules/url-parse": { 60 | "version": "1.5.10", 61 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 62 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 63 | "license": "MIT", 64 | "dependencies": { 65 | "querystringify": "^2.1.1", 66 | "requires-port": "^1.0.0" 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /product-event-listener/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product-event-listener", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "product-event-listener", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "amqplib": "^0.10.8", 13 | "dotenv": "^16.5.0" 14 | } 15 | }, 16 | "node_modules/amqplib": { 17 | "version": "0.10.8", 18 | "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.8.tgz", 19 | "integrity": "sha512-Tfn1O9sFgAP8DqeMEpt2IacsVTENBpblB3SqLdn0jK2AeX8iyCvbptBc8lyATT9bQ31MsjVwUSQ1g8f4jHOUfw==", 20 | "license": "MIT", 21 | "dependencies": { 22 | "buffer-more-ints": "~1.0.0", 23 | "url-parse": "~1.5.10" 24 | }, 25 | "engines": { 26 | "node": ">=10" 27 | } 28 | }, 29 | "node_modules/buffer-more-ints": { 30 | "version": "1.0.0", 31 | "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", 32 | "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", 33 | "license": "MIT" 34 | }, 35 | "node_modules/dotenv": { 36 | "version": "16.5.0", 37 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", 38 | "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", 39 | "license": "BSD-2-Clause", 40 | "engines": { 41 | "node": ">=12" 42 | }, 43 | "funding": { 44 | "url": "https://dotenvx.com" 45 | } 46 | }, 47 | "node_modules/querystringify": { 48 | "version": "2.2.0", 49 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 50 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", 51 | "license": "MIT" 52 | }, 53 | "node_modules/requires-port": { 54 | "version": "1.0.0", 55 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 56 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", 57 | "license": "MIT" 58 | }, 59 | "node_modules/url-parse": { 60 | "version": "1.5.10", 61 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 62 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 63 | "license": "MIT", 64 | "dependencies": { 65 | "querystringify": "^2.1.1", 66 | "requires-port": "^1.0.0" 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | .clinic/ 132 | features.md 133 | .qodo 134 | -------------------------------------------------------------------------------- /inventory-service/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | .clinic/ 132 | features.md 133 | .qodo 134 | -------------------------------------------------------------------------------- /product-service/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | .clinic/ 132 | features.md 133 | .qodo 134 | -------------------------------------------------------------------------------- /notification-service/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | .clinic/ 132 | features.md 133 | .qodo 134 | -------------------------------------------------------------------------------- /product-event-listener/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | .clinic/ 132 | features.md 133 | .qodo 134 | -------------------------------------------------------------------------------- /user-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "user-service", 3 | "version": "0.0.1", 4 | "description": "", 5 | "author": "", 6 | "private": true, 7 | "license": "UNLICENSED", 8 | "scripts": { 9 | "build": "nest build", 10 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", 11 | "start": "node dist/main", 12 | "start:dev": "nest start --watch", 13 | "start:debug": "nest start --debug --watch", 14 | "start:prod": "node dist/main", 15 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", 16 | "test": "jest", 17 | "test:watch": "jest --watch", 18 | "test:cov": "jest --coverage", 19 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", 20 | "test:e2e": "jest --config ./test/jest-e2e.json" 21 | }, 22 | "dependencies": { 23 | "@nestjs/common": "^11.0.1", 24 | "@nestjs/config": "^4.0.2", 25 | "@nestjs/core": "^11.0.1", 26 | "@nestjs/jwt": "^11.0.0", 27 | "@nestjs/passport": "^11.0.5", 28 | "@nestjs/platform-express": "^11.0.1", 29 | "@nestjs/swagger": "^11.2.0", 30 | "@nestjs/typeorm": "^11.0.0", 31 | "bcrypt": "^6.0.0", 32 | "class-transformer": "^0.5.1", 33 | "class-validator": "^0.14.2", 34 | "passport": "^0.7.0", 35 | "passport-jwt": "^4.0.1", 36 | "pg": "^8.16.0", 37 | "reflect-metadata": "^0.2.2", 38 | "rxjs": "^7.8.1", 39 | "swagger": "^0.7.5", 40 | "typeorm": "^0.3.24" 41 | }, 42 | "devDependencies": { 43 | "@eslint/eslintrc": "^3.2.0", 44 | "@eslint/js": "^9.18.0", 45 | "@nestjs/cli": "^11.0.0", 46 | "@nestjs/schematics": "^11.0.0", 47 | "@nestjs/testing": "^11.0.1", 48 | "@swc/cli": "^0.6.0", 49 | "@swc/core": "^1.10.7", 50 | "@types/bcrypt": "^5.0.2", 51 | "@types/express": "^5.0.0", 52 | "@types/jest": "^29.5.14", 53 | "@types/node": "^22.10.7", 54 | "@types/passport-jwt": "^4.0.1", 55 | "@types/supertest": "^6.0.2", 56 | "eslint": "^9.18.0", 57 | "eslint-config-prettier": "^10.0.1", 58 | "eslint-plugin-prettier": "^5.2.2", 59 | "globals": "^16.0.0", 60 | "jest": "^29.7.0", 61 | "prettier": "^3.4.2", 62 | "source-map-support": "^0.5.21", 63 | "supertest": "^7.0.0", 64 | "ts-jest": "^29.2.5", 65 | "ts-loader": "^9.5.2", 66 | "ts-node": "^10.9.2", 67 | "tsconfig-paths": "^4.2.0", 68 | "typescript": "^5.7.3", 69 | "typescript-eslint": "^8.20.0" 70 | }, 71 | "jest": { 72 | "moduleFileExtensions": [ 73 | "js", 74 | "json", 75 | "ts" 76 | ], 77 | "rootDir": "src", 78 | "testRegex": ".*\\.spec\\.ts$", 79 | "transform": { 80 | "^.+\\.(t|j)s$": "ts-jest" 81 | }, 82 | "collectCoverageFrom": [ 83 | "**/*.(t|j)s" 84 | ], 85 | "coverageDirectory": "../coverage", 86 | "testEnvironment": "node" 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | mongodb: 5 | image: mongo:6 6 | volumes: 7 | - mongo_data:/data/db 8 | ports: 9 | - "9092:27017" 10 | networks: 11 | nodejs_network: 12 | ipv4_address: 172.21.0.2 13 | 14 | rabbitmq: 15 | image: rabbitmq:3-management 16 | environment: 17 | RABBITMQ_DEFAULT_USER: guest 18 | RABBITMQ_DEFAULT_PASS: guest 19 | volumes: 20 | - rabbitmq_data:/var/lib/rabbitmq 21 | ports: 22 | - "9093:5672" 23 | - "9094:15672" 24 | networks: 25 | nodejs_network: 26 | ipv4_address: 172.21.0.3 27 | 28 | postgres: 29 | image: postgres:15 30 | environment: 31 | POSTGRES_USER: postgres 32 | POSTGRES_PASSWORD: postgres 33 | volumes: 34 | - postgres_data:/var/lib/postgresql/data 35 | ports: 36 | - "9091:5432" 37 | networks: 38 | nodejs_network: 39 | ipv4_address: 172.21.0.4 40 | 41 | nginx: 42 | image: nginx:alpine 43 | depends_on: 44 | - api-gateway 45 | ports: 46 | - "90:80" 47 | volumes: 48 | - ./nginx/conf.d:/etc/nginx/conf.d 49 | networks: 50 | nodejs_network: 51 | ipv4_address: 172.21.0.5 52 | 53 | user-service: 54 | build: ./user-service 55 | restart: always 56 | environment: 57 | DATABASE_URL: postgres://postgres:postgres@postgres:5432/userdb 58 | RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672 59 | ports: 60 | - "3010:3000" 61 | depends_on: 62 | - postgres 63 | - rabbitmq 64 | networks: 65 | nodejs_network: 66 | ipv4_address: 172.21.0.6 67 | 68 | product-service: 69 | build: ./product-service 70 | restart: always 71 | environment: 72 | MONGODB_URL: mongodb://mongodb:27017/product-db 73 | RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672 74 | ports: 75 | - "3004:3000" 76 | depends_on: 77 | - mongodb 78 | - rabbitmq 79 | networks: 80 | nodejs_network: 81 | ipv4_address: 172.21.0.7 82 | 83 | product-event-listener: 84 | build: ./product-event-listener 85 | restart: always 86 | ports: 87 | - "30014:3000" 88 | depends_on: 89 | - rabbitmq 90 | networks: 91 | nodejs_network: 92 | ipv4_address: 172.21.0.12 93 | 94 | order-service: 95 | build: ./order-service 96 | restart: always 97 | environment: 98 | DATABASE_URL: postgres://postgres:postgres@postgres:5432/orders 99 | RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672 100 | ports: 101 | - "3009:3000" 102 | depends_on: 103 | - postgres 104 | - rabbitmq 105 | networks: 106 | nodejs_network: 107 | ipv4_address: 172.21.0.8 108 | 109 | order-event-listener: 110 | build: ./order-event-listener 111 | restart: always 112 | ports: 113 | - "3013:3000" 114 | depends_on: 115 | - rabbitmq 116 | networks: 117 | nodejs_network: 118 | ipv4_address: 172.21.0.13 119 | 120 | inventory-service: 121 | build: ./inventory-service 122 | restart: always 123 | environment: 124 | MONGO_URI: mongodb://mongodb:27017/inventory 125 | RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672 126 | ports: 127 | - "3007:3000" 128 | depends_on: 129 | - mongodb 130 | - rabbitmq 131 | networks: 132 | nodejs_network: 133 | ipv4_address: 172.21.0.9 134 | 135 | notification-service: 136 | build: ./notification-service 137 | restart: always 138 | environment: 139 | RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672 140 | ports: 141 | - "3005:3000" 142 | depends_on: 143 | - rabbitmq 144 | networks: 145 | nodejs_network: 146 | ipv4_address: 172.21.0.10 147 | 148 | api-gateway: 149 | build: ./api-gateway 150 | environment: 151 | USER_SERVICE_URL: http://user-service:3010 152 | PRODUCT_SERVICE_URL: http://product-service:3004 153 | ORDER_SERVICE_URL: http://order-service:3009 154 | INVENTORY_SERVICE_URL: http://inventory-service:3007 155 | NOTIFICATION_SERVICE_URL: http://notification-service:3005 156 | ports: 157 | - "3011:3000" 158 | depends_on: 159 | - user-service 160 | - product-service 161 | - order-service 162 | - inventory-service 163 | - notification-service 164 | networks: 165 | nodejs_network: 166 | ipv4_address: 172.21.0.11 167 | 168 | networks: 169 | nodejs_network: 170 | external: true 171 | 172 | volumes: 173 | postgres_data: 174 | mongo_data: 175 | rabbitmq_data: -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛒 NodeJS Microservices: Order & Inventory System 2 | 3 | ![Repository Size](https://img.shields.io/github/repo-size/JawherKl/nodejs-microservices) 4 | ![Last Commit](https://img.shields.io/github/last-commit/JawherKl/nodejs-microservices) 5 | ![Issues](https://img.shields.io/github/issues-raw/JawherKl/nodejs-microservices) 6 | ![Forks](https://img.shields.io/github/forks/JawherKl/nodejs-microservices) 7 | ![Stars](https://img.shields.io/github/stars/JawherKl/nodejs-microservices) 8 | 9 | Scalable and modular microservices architecture built with Node.js (NestJS & ExpressJS), PostgreSQL, MongoDB and Docker. It leverages RabbitMQ for event-driven communication, ideal for modern e-commerce and real-time systems. 10 | 11 | ![NodeJS](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white) 12 | ![NestJS](https://img.shields.io/badge/nestjs-E0234E?style=for-the-badge&logo=nestjs&logoColor=white) 13 | ![ExpressJS](https://img.shields.io/badge/express.js-000000?style=for-the-badge&logo=express&logoColor=white) 14 | ![PostgreSQL](https://img.shields.io/badge/postgres-%23316192.svg?style=for-the-badge&logo=postgresql&logoColor=white) 15 | ![MongoDB](https://img.shields.io/badge/mongodb-4EA94B?style=for-the-badge&logo=mongodb&logoColor=white) 16 | ![RabbitMQ](https://img.shields.io/badge/rabbitmq-FF6600?style=for-the-badge&logo=rabbitmq&logoColor=white) 17 | ![Docker](https://img.shields.io/badge/docker-2496ED?style=for-the-badge&logo=docker&logoColor=white) 18 | 19 | --- 20 | 21 | nodejs-microservice 22 | 23 | ## ✨ Overview 24 | 25 | This project demonstrates a modern microservices architecture for an Order & Inventory System, suitable for real-world companies. Each service is designed with a single responsibility, clear separation, and scalable technologies. 26 | 27 | - **Tech Focus:** TypeScript-first, RESTful APIs, event-driven messaging, containerised infrastructure 28 | - **Monorepo:** All services managed together for easy orchestration and development 29 | - **Best Practices:** Security, scalability, and maintainability in mind 30 | 31 | --- 32 | 33 | ## 🏗️ Architecture 34 | 35 | ```mermaid 36 | graph TB 37 | %% Application Layer 38 | subgraph "Application Layer" 39 | agw[API Gateway] 40 | us[User Service
NestJS] 41 | ps[Product Service
ExpressJS] 42 | os[Order Service
NestJS] 43 | isvc[Inventory Service
ExpressJS] 44 | ns[Notification Service
ExpressJS] 45 | end 46 | 47 | %% Database Layer 48 | subgraph "Database Layer" 49 | pg[(PostgreSQL)] 50 | mongo[(MongoDB)] 51 | end 52 | 53 | %% Messaging Layer 54 | subgraph "Messaging" 55 | rmq[[RabbitMQ
Message Broker]] 56 | end 57 | 58 | %% Dependencies and Connections 59 | agw -->|REST| us 60 | agw -->|REST| ps 61 | agw -->|REST| os 62 | agw -->|REST| isvc 63 | agw -->|REST| ns 64 | 65 | us -- "DATABASE_URL" --> pg 66 | os -- "DATABASE_URL" --> pg 67 | ps -- "mongodb://..." --> mongo 68 | isvc -- "mongodb://..." --> mongo 69 | ns -- "mongodb://..." --> mongo 70 | 71 | us -- "RABBITMQ_URL" --> rmq 72 | os -- "RABBITMQ_URL" --> rmq 73 | isvc -- "RABBITMQ_URL" --> rmq 74 | ns -- "RABBITMQ_URL" --> rmq 75 | 76 | %% Messaging Events 77 | rmq -- "Events" --> os 78 | rmq -- "Events" --> isvc 79 | rmq -- "Events" --> ns 80 | 81 | %% External Ports Exposure 82 | agw -->|":3000"| extgw[External Access] 83 | us -->|":3001"| extus[External Access] 84 | ps -->|":3002"| extps[External Access] 85 | os -->|":3003"| extos[External Access] 86 | isvc -->|":3004"| extis[External Access] 87 | ns -->|":3005"| extns[External Access] 88 | pg -->|":9091"| extpg[External Access] 89 | mongo -->|":9092"| extmongo[External Access] 90 | rmq -->|":9093/9094"| extrmq[External Access] 91 | 92 | %% Class Styling 93 | classDef app fill:#2ecc71,stroke:#27ae60,color:white 94 | classDef db fill:#3498db,stroke:#2980b9,color:white 95 | classDef msg fill:#e67e22,stroke:#d35400,color:white 96 | classDef ext fill:#95a5a6,stroke:#7f8c8d,color:white 97 | 98 | class agw,us,ps,os,isvc,ns app 99 | class pg,mongo db 100 | class rmq msg 101 | class extgw,extus,extps,extos,extis,extns,extpg,extmongo,extrmq ext 102 | ``` 103 | 104 | | Microservice | Tech Stack | DB | Responsibility | 105 | | ------------------------ | ---------------- | ---------- | ----------------------------------------------- | 106 | | **User Service** | NestJS | PostgreSQL | User registration, login, authentication, roles | 107 | | **Product Service** | ExpressJS | MongoDB | Product CRUD (create, update, list, delete) | 108 | | **Order Service** | NestJS | PostgreSQL | Order creation, validation, and processing | 109 | | **Inventory Service** | ExpressJS | MongoDB | Stock tracking, update on orders | 110 | | **Notification Service** | ExpressJS | MongoDB | Email/log notifications on events | 111 | | **API Gateway** | ExpressJS/NestJS | - | Unified API entry, routing, token verification | 112 | | **Message Broker** | RabbitMQ | - | Inter-service event bus | 113 | 114 | --- 115 | 116 | ## 📁 Project Structure 117 | 118 | ```bash 119 | nodejs-microservices/ 120 | ├── api-gateway/ # API Gateway (routing, auth) 121 | ├── user-service/ # User microservice 122 | ├── product-service/ # Product microservice 123 | ├── order-service/ # Order microservice 124 | ├── inventory-service/ # Inventory microservice 125 | ├── notification-service/ # Notification microservice 126 | ├── docker-compose.yml # Infrastructure orchestration 127 | ├── docs/ # Architecture diagrams, docs 128 | ├── README.md 129 | ``` 130 | 131 | --- 132 | 133 | ## 🔗 Communication 134 | 135 | * **API Gateway ⇄ Services:** REST (HTTP) 136 | * **Between Services:** Messaging (RabbitMQ) 137 | 138 | * `OrderService` emits `order_created` 139 | * `InventoryService` listens and adjusts stock 140 | * `NotificationService` listens and sends notification 141 | 142 | --- 143 | 144 | ## 🚀 Features 145 | 146 | ### 👤 User Service 147 | 148 | * Secure sign up & login 149 | * JWT authentication 150 | * Role-based access (admin/user) 151 | 152 | ### 🛍️ Product Service 153 | 154 | * Create/list/delete products 155 | * Fetch by product ID 156 | * Simple MongoDB schema 157 | 158 | ### 🛒 Order Service 159 | 160 | * Place new orders 161 | * Validate users & product stock 162 | * Emits `order_created` event 163 | 164 | ### 🏬 Inventory Service 165 | 166 | * Listens to `order_created` 167 | * Decreases stock or rejects if insufficient 168 | 169 | ### 📢 Notification Service 170 | 171 | * Listens to events (`order_created`, etc) 172 | * Sends notification (email/log) 173 | 174 | ### 🌍 API Gateway 175 | 176 | * One entry point for frontend 177 | * Reverse proxy, JWT verification 178 | * Routes `/products`, `/orders`, `/users` to services 179 | 180 | --- 181 | 182 | ## 🐳 Dockerized Infrastructure 183 | 184 | Powered by `docker-compose.yml`: 185 | 186 | * All microservices (with build contexts) 187 | * MongoDB, PostgreSQL databases 188 | * RabbitMQ for messaging 189 | * Optional admin UIs: pgAdmin, Mongo Express, RabbitMQ dashboard 190 | 191 | ### 🧪 Docker Network & Setup 192 | 193 | Before starting the app, **create the Docker network**: 194 | 195 | ```bash 196 | docker network create \ 197 | --driver=bridge \ 198 | --subnet=172.21.0.0/24 \ 199 | nodejs_network 200 | ``` 201 | 202 | Then run the entire system: 203 | 204 | ```bash 205 | sudo docker-compose up --build -d 206 | ``` 207 | 208 | All services will be available on their respective internal IP addresses and ports, or via the API Gateway. 209 | 210 | --- 211 | 212 | ## 🛣️ Roadmap 213 | 214 | * [ ] Add OpenAPI/Swagger docs for each service 215 | * [ ] Service discovery and dynamic routing 216 | * [ ] Production-ready monitoring & logging 217 | * [ ] Integration with frontend (React/Angular) 218 | * [ ] Advanced notification channels (SMS, Push) 219 | * [ ] Create CI/CD Pipeline with GitHub Actions 220 | 221 | --- 222 | 223 | ## 🤝 Contributing 224 | 225 | We welcome contributions, ideas, and feedback. 226 | Feel free to open issues or submit pull requests. 227 | 228 | --- 229 | 230 | ## 📜 License 231 | 232 | MIT License. See [LICENSE](LICENSE) for details. 233 | 234 | --- 235 | 236 | ## 👨‍💻 Author 237 | 238 | **Jawher Kallel** 239 | [GitHub @JawherKl](https://github.com/JawherKl) 240 | 241 | --- 242 | 243 | Made with ❤️ for scalable, real-world systems. 244 | 245 | ## 🌟 Stargazers over time 246 | 247 | [![Stargazers over time](https://starchart.cc/JawherKl/nodejs-microservices.svg?variant=adaptive)](https://starchart.cc/JawherKl/nodejs-microservices) 248 | -------------------------------------------------------------------------------- /notification-service/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "notification-service", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "notification-service", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "amqplib": "^0.10.3", 13 | "dotenv": "^16.3.1", 14 | "express": "^4.18.2" 15 | } 16 | }, 17 | "node_modules/accepts": { 18 | "version": "1.3.8", 19 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 20 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 21 | "license": "MIT", 22 | "dependencies": { 23 | "mime-types": "~2.1.34", 24 | "negotiator": "0.6.3" 25 | }, 26 | "engines": { 27 | "node": ">= 0.6" 28 | } 29 | }, 30 | "node_modules/amqplib": { 31 | "version": "0.10.8", 32 | "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.8.tgz", 33 | "integrity": "sha512-Tfn1O9sFgAP8DqeMEpt2IacsVTENBpblB3SqLdn0jK2AeX8iyCvbptBc8lyATT9bQ31MsjVwUSQ1g8f4jHOUfw==", 34 | "license": "MIT", 35 | "dependencies": { 36 | "buffer-more-ints": "~1.0.0", 37 | "url-parse": "~1.5.10" 38 | }, 39 | "engines": { 40 | "node": ">=10" 41 | } 42 | }, 43 | "node_modules/array-flatten": { 44 | "version": "1.1.1", 45 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 46 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", 47 | "license": "MIT" 48 | }, 49 | "node_modules/body-parser": { 50 | "version": "1.20.3", 51 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", 52 | "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", 53 | "license": "MIT", 54 | "dependencies": { 55 | "bytes": "3.1.2", 56 | "content-type": "~1.0.5", 57 | "debug": "2.6.9", 58 | "depd": "2.0.0", 59 | "destroy": "1.2.0", 60 | "http-errors": "2.0.0", 61 | "iconv-lite": "0.4.24", 62 | "on-finished": "2.4.1", 63 | "qs": "6.13.0", 64 | "raw-body": "2.5.2", 65 | "type-is": "~1.6.18", 66 | "unpipe": "1.0.0" 67 | }, 68 | "engines": { 69 | "node": ">= 0.8", 70 | "npm": "1.2.8000 || >= 1.4.16" 71 | } 72 | }, 73 | "node_modules/buffer-more-ints": { 74 | "version": "1.0.0", 75 | "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", 76 | "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", 77 | "license": "MIT" 78 | }, 79 | "node_modules/bytes": { 80 | "version": "3.1.2", 81 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 82 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 83 | "license": "MIT", 84 | "engines": { 85 | "node": ">= 0.8" 86 | } 87 | }, 88 | "node_modules/call-bind-apply-helpers": { 89 | "version": "1.0.2", 90 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 91 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 92 | "license": "MIT", 93 | "dependencies": { 94 | "es-errors": "^1.3.0", 95 | "function-bind": "^1.1.2" 96 | }, 97 | "engines": { 98 | "node": ">= 0.4" 99 | } 100 | }, 101 | "node_modules/call-bound": { 102 | "version": "1.0.4", 103 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 104 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 105 | "license": "MIT", 106 | "dependencies": { 107 | "call-bind-apply-helpers": "^1.0.2", 108 | "get-intrinsic": "^1.3.0" 109 | }, 110 | "engines": { 111 | "node": ">= 0.4" 112 | }, 113 | "funding": { 114 | "url": "https://github.com/sponsors/ljharb" 115 | } 116 | }, 117 | "node_modules/content-disposition": { 118 | "version": "0.5.4", 119 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 120 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 121 | "license": "MIT", 122 | "dependencies": { 123 | "safe-buffer": "5.2.1" 124 | }, 125 | "engines": { 126 | "node": ">= 0.6" 127 | } 128 | }, 129 | "node_modules/content-type": { 130 | "version": "1.0.5", 131 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 132 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 133 | "license": "MIT", 134 | "engines": { 135 | "node": ">= 0.6" 136 | } 137 | }, 138 | "node_modules/cookie": { 139 | "version": "0.7.1", 140 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", 141 | "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", 142 | "license": "MIT", 143 | "engines": { 144 | "node": ">= 0.6" 145 | } 146 | }, 147 | "node_modules/cookie-signature": { 148 | "version": "1.0.6", 149 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 150 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", 151 | "license": "MIT" 152 | }, 153 | "node_modules/debug": { 154 | "version": "2.6.9", 155 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 156 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 157 | "license": "MIT", 158 | "dependencies": { 159 | "ms": "2.0.0" 160 | } 161 | }, 162 | "node_modules/depd": { 163 | "version": "2.0.0", 164 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 165 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 166 | "license": "MIT", 167 | "engines": { 168 | "node": ">= 0.8" 169 | } 170 | }, 171 | "node_modules/destroy": { 172 | "version": "1.2.0", 173 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 174 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 175 | "license": "MIT", 176 | "engines": { 177 | "node": ">= 0.8", 178 | "npm": "1.2.8000 || >= 1.4.16" 179 | } 180 | }, 181 | "node_modules/dotenv": { 182 | "version": "16.5.0", 183 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", 184 | "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", 185 | "license": "BSD-2-Clause", 186 | "engines": { 187 | "node": ">=12" 188 | }, 189 | "funding": { 190 | "url": "https://dotenvx.com" 191 | } 192 | }, 193 | "node_modules/dunder-proto": { 194 | "version": "1.0.1", 195 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 196 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 197 | "license": "MIT", 198 | "dependencies": { 199 | "call-bind-apply-helpers": "^1.0.1", 200 | "es-errors": "^1.3.0", 201 | "gopd": "^1.2.0" 202 | }, 203 | "engines": { 204 | "node": ">= 0.4" 205 | } 206 | }, 207 | "node_modules/ee-first": { 208 | "version": "1.1.1", 209 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 210 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 211 | "license": "MIT" 212 | }, 213 | "node_modules/encodeurl": { 214 | "version": "2.0.0", 215 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 216 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 217 | "license": "MIT", 218 | "engines": { 219 | "node": ">= 0.8" 220 | } 221 | }, 222 | "node_modules/es-define-property": { 223 | "version": "1.0.1", 224 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 225 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 226 | "license": "MIT", 227 | "engines": { 228 | "node": ">= 0.4" 229 | } 230 | }, 231 | "node_modules/es-errors": { 232 | "version": "1.3.0", 233 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 234 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 235 | "license": "MIT", 236 | "engines": { 237 | "node": ">= 0.4" 238 | } 239 | }, 240 | "node_modules/es-object-atoms": { 241 | "version": "1.1.1", 242 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 243 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 244 | "license": "MIT", 245 | "dependencies": { 246 | "es-errors": "^1.3.0" 247 | }, 248 | "engines": { 249 | "node": ">= 0.4" 250 | } 251 | }, 252 | "node_modules/escape-html": { 253 | "version": "1.0.3", 254 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 255 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 256 | "license": "MIT" 257 | }, 258 | "node_modules/etag": { 259 | "version": "1.8.1", 260 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 261 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 262 | "license": "MIT", 263 | "engines": { 264 | "node": ">= 0.6" 265 | } 266 | }, 267 | "node_modules/express": { 268 | "version": "4.21.2", 269 | "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", 270 | "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 271 | "license": "MIT", 272 | "dependencies": { 273 | "accepts": "~1.3.8", 274 | "array-flatten": "1.1.1", 275 | "body-parser": "1.20.3", 276 | "content-disposition": "0.5.4", 277 | "content-type": "~1.0.4", 278 | "cookie": "0.7.1", 279 | "cookie-signature": "1.0.6", 280 | "debug": "2.6.9", 281 | "depd": "2.0.0", 282 | "encodeurl": "~2.0.0", 283 | "escape-html": "~1.0.3", 284 | "etag": "~1.8.1", 285 | "finalhandler": "1.3.1", 286 | "fresh": "0.5.2", 287 | "http-errors": "2.0.0", 288 | "merge-descriptors": "1.0.3", 289 | "methods": "~1.1.2", 290 | "on-finished": "2.4.1", 291 | "parseurl": "~1.3.3", 292 | "path-to-regexp": "0.1.12", 293 | "proxy-addr": "~2.0.7", 294 | "qs": "6.13.0", 295 | "range-parser": "~1.2.1", 296 | "safe-buffer": "5.2.1", 297 | "send": "0.19.0", 298 | "serve-static": "1.16.2", 299 | "setprototypeof": "1.2.0", 300 | "statuses": "2.0.1", 301 | "type-is": "~1.6.18", 302 | "utils-merge": "1.0.1", 303 | "vary": "~1.1.2" 304 | }, 305 | "engines": { 306 | "node": ">= 0.10.0" 307 | }, 308 | "funding": { 309 | "type": "opencollective", 310 | "url": "https://opencollective.com/express" 311 | } 312 | }, 313 | "node_modules/finalhandler": { 314 | "version": "1.3.1", 315 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", 316 | "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", 317 | "license": "MIT", 318 | "dependencies": { 319 | "debug": "2.6.9", 320 | "encodeurl": "~2.0.0", 321 | "escape-html": "~1.0.3", 322 | "on-finished": "2.4.1", 323 | "parseurl": "~1.3.3", 324 | "statuses": "2.0.1", 325 | "unpipe": "~1.0.0" 326 | }, 327 | "engines": { 328 | "node": ">= 0.8" 329 | } 330 | }, 331 | "node_modules/forwarded": { 332 | "version": "0.2.0", 333 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 334 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 335 | "license": "MIT", 336 | "engines": { 337 | "node": ">= 0.6" 338 | } 339 | }, 340 | "node_modules/fresh": { 341 | "version": "0.5.2", 342 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 343 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", 344 | "license": "MIT", 345 | "engines": { 346 | "node": ">= 0.6" 347 | } 348 | }, 349 | "node_modules/function-bind": { 350 | "version": "1.1.2", 351 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 352 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 353 | "license": "MIT", 354 | "funding": { 355 | "url": "https://github.com/sponsors/ljharb" 356 | } 357 | }, 358 | "node_modules/get-intrinsic": { 359 | "version": "1.3.0", 360 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 361 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 362 | "license": "MIT", 363 | "dependencies": { 364 | "call-bind-apply-helpers": "^1.0.2", 365 | "es-define-property": "^1.0.1", 366 | "es-errors": "^1.3.0", 367 | "es-object-atoms": "^1.1.1", 368 | "function-bind": "^1.1.2", 369 | "get-proto": "^1.0.1", 370 | "gopd": "^1.2.0", 371 | "has-symbols": "^1.1.0", 372 | "hasown": "^2.0.2", 373 | "math-intrinsics": "^1.1.0" 374 | }, 375 | "engines": { 376 | "node": ">= 0.4" 377 | }, 378 | "funding": { 379 | "url": "https://github.com/sponsors/ljharb" 380 | } 381 | }, 382 | "node_modules/get-proto": { 383 | "version": "1.0.1", 384 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 385 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 386 | "license": "MIT", 387 | "dependencies": { 388 | "dunder-proto": "^1.0.1", 389 | "es-object-atoms": "^1.0.0" 390 | }, 391 | "engines": { 392 | "node": ">= 0.4" 393 | } 394 | }, 395 | "node_modules/gopd": { 396 | "version": "1.2.0", 397 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 398 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 399 | "license": "MIT", 400 | "engines": { 401 | "node": ">= 0.4" 402 | }, 403 | "funding": { 404 | "url": "https://github.com/sponsors/ljharb" 405 | } 406 | }, 407 | "node_modules/has-symbols": { 408 | "version": "1.1.0", 409 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 410 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 411 | "license": "MIT", 412 | "engines": { 413 | "node": ">= 0.4" 414 | }, 415 | "funding": { 416 | "url": "https://github.com/sponsors/ljharb" 417 | } 418 | }, 419 | "node_modules/hasown": { 420 | "version": "2.0.2", 421 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 422 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 423 | "license": "MIT", 424 | "dependencies": { 425 | "function-bind": "^1.1.2" 426 | }, 427 | "engines": { 428 | "node": ">= 0.4" 429 | } 430 | }, 431 | "node_modules/http-errors": { 432 | "version": "2.0.0", 433 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 434 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 435 | "license": "MIT", 436 | "dependencies": { 437 | "depd": "2.0.0", 438 | "inherits": "2.0.4", 439 | "setprototypeof": "1.2.0", 440 | "statuses": "2.0.1", 441 | "toidentifier": "1.0.1" 442 | }, 443 | "engines": { 444 | "node": ">= 0.8" 445 | } 446 | }, 447 | "node_modules/iconv-lite": { 448 | "version": "0.4.24", 449 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 450 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 451 | "license": "MIT", 452 | "dependencies": { 453 | "safer-buffer": ">= 2.1.2 < 3" 454 | }, 455 | "engines": { 456 | "node": ">=0.10.0" 457 | } 458 | }, 459 | "node_modules/inherits": { 460 | "version": "2.0.4", 461 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 462 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 463 | "license": "ISC" 464 | }, 465 | "node_modules/ipaddr.js": { 466 | "version": "1.9.1", 467 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 468 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 469 | "license": "MIT", 470 | "engines": { 471 | "node": ">= 0.10" 472 | } 473 | }, 474 | "node_modules/math-intrinsics": { 475 | "version": "1.1.0", 476 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 477 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 478 | "license": "MIT", 479 | "engines": { 480 | "node": ">= 0.4" 481 | } 482 | }, 483 | "node_modules/media-typer": { 484 | "version": "0.3.0", 485 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 486 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", 487 | "license": "MIT", 488 | "engines": { 489 | "node": ">= 0.6" 490 | } 491 | }, 492 | "node_modules/merge-descriptors": { 493 | "version": "1.0.3", 494 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", 495 | "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", 496 | "license": "MIT", 497 | "funding": { 498 | "url": "https://github.com/sponsors/sindresorhus" 499 | } 500 | }, 501 | "node_modules/methods": { 502 | "version": "1.1.2", 503 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 504 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", 505 | "license": "MIT", 506 | "engines": { 507 | "node": ">= 0.6" 508 | } 509 | }, 510 | "node_modules/mime": { 511 | "version": "1.6.0", 512 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 513 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 514 | "license": "MIT", 515 | "bin": { 516 | "mime": "cli.js" 517 | }, 518 | "engines": { 519 | "node": ">=4" 520 | } 521 | }, 522 | "node_modules/mime-db": { 523 | "version": "1.52.0", 524 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 525 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 526 | "license": "MIT", 527 | "engines": { 528 | "node": ">= 0.6" 529 | } 530 | }, 531 | "node_modules/mime-types": { 532 | "version": "2.1.35", 533 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 534 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 535 | "license": "MIT", 536 | "dependencies": { 537 | "mime-db": "1.52.0" 538 | }, 539 | "engines": { 540 | "node": ">= 0.6" 541 | } 542 | }, 543 | "node_modules/ms": { 544 | "version": "2.0.0", 545 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 546 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 547 | "license": "MIT" 548 | }, 549 | "node_modules/negotiator": { 550 | "version": "0.6.3", 551 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 552 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 553 | "license": "MIT", 554 | "engines": { 555 | "node": ">= 0.6" 556 | } 557 | }, 558 | "node_modules/object-inspect": { 559 | "version": "1.13.4", 560 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 561 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 562 | "license": "MIT", 563 | "engines": { 564 | "node": ">= 0.4" 565 | }, 566 | "funding": { 567 | "url": "https://github.com/sponsors/ljharb" 568 | } 569 | }, 570 | "node_modules/on-finished": { 571 | "version": "2.4.1", 572 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 573 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 574 | "license": "MIT", 575 | "dependencies": { 576 | "ee-first": "1.1.1" 577 | }, 578 | "engines": { 579 | "node": ">= 0.8" 580 | } 581 | }, 582 | "node_modules/parseurl": { 583 | "version": "1.3.3", 584 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 585 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 586 | "license": "MIT", 587 | "engines": { 588 | "node": ">= 0.8" 589 | } 590 | }, 591 | "node_modules/path-to-regexp": { 592 | "version": "0.1.12", 593 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", 594 | "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", 595 | "license": "MIT" 596 | }, 597 | "node_modules/proxy-addr": { 598 | "version": "2.0.7", 599 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 600 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 601 | "license": "MIT", 602 | "dependencies": { 603 | "forwarded": "0.2.0", 604 | "ipaddr.js": "1.9.1" 605 | }, 606 | "engines": { 607 | "node": ">= 0.10" 608 | } 609 | }, 610 | "node_modules/qs": { 611 | "version": "6.13.0", 612 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", 613 | "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 614 | "license": "BSD-3-Clause", 615 | "dependencies": { 616 | "side-channel": "^1.0.6" 617 | }, 618 | "engines": { 619 | "node": ">=0.6" 620 | }, 621 | "funding": { 622 | "url": "https://github.com/sponsors/ljharb" 623 | } 624 | }, 625 | "node_modules/querystringify": { 626 | "version": "2.2.0", 627 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 628 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", 629 | "license": "MIT" 630 | }, 631 | "node_modules/range-parser": { 632 | "version": "1.2.1", 633 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 634 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 635 | "license": "MIT", 636 | "engines": { 637 | "node": ">= 0.6" 638 | } 639 | }, 640 | "node_modules/raw-body": { 641 | "version": "2.5.2", 642 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", 643 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", 644 | "license": "MIT", 645 | "dependencies": { 646 | "bytes": "3.1.2", 647 | "http-errors": "2.0.0", 648 | "iconv-lite": "0.4.24", 649 | "unpipe": "1.0.0" 650 | }, 651 | "engines": { 652 | "node": ">= 0.8" 653 | } 654 | }, 655 | "node_modules/requires-port": { 656 | "version": "1.0.0", 657 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 658 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", 659 | "license": "MIT" 660 | }, 661 | "node_modules/safe-buffer": { 662 | "version": "5.2.1", 663 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 664 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 665 | "funding": [ 666 | { 667 | "type": "github", 668 | "url": "https://github.com/sponsors/feross" 669 | }, 670 | { 671 | "type": "patreon", 672 | "url": "https://www.patreon.com/feross" 673 | }, 674 | { 675 | "type": "consulting", 676 | "url": "https://feross.org/support" 677 | } 678 | ], 679 | "license": "MIT" 680 | }, 681 | "node_modules/safer-buffer": { 682 | "version": "2.1.2", 683 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 684 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 685 | "license": "MIT" 686 | }, 687 | "node_modules/send": { 688 | "version": "0.19.0", 689 | "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", 690 | "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 691 | "license": "MIT", 692 | "dependencies": { 693 | "debug": "2.6.9", 694 | "depd": "2.0.0", 695 | "destroy": "1.2.0", 696 | "encodeurl": "~1.0.2", 697 | "escape-html": "~1.0.3", 698 | "etag": "~1.8.1", 699 | "fresh": "0.5.2", 700 | "http-errors": "2.0.0", 701 | "mime": "1.6.0", 702 | "ms": "2.1.3", 703 | "on-finished": "2.4.1", 704 | "range-parser": "~1.2.1", 705 | "statuses": "2.0.1" 706 | }, 707 | "engines": { 708 | "node": ">= 0.8.0" 709 | } 710 | }, 711 | "node_modules/send/node_modules/encodeurl": { 712 | "version": "1.0.2", 713 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 714 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", 715 | "license": "MIT", 716 | "engines": { 717 | "node": ">= 0.8" 718 | } 719 | }, 720 | "node_modules/send/node_modules/ms": { 721 | "version": "2.1.3", 722 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 723 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 724 | "license": "MIT" 725 | }, 726 | "node_modules/serve-static": { 727 | "version": "1.16.2", 728 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", 729 | "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", 730 | "license": "MIT", 731 | "dependencies": { 732 | "encodeurl": "~2.0.0", 733 | "escape-html": "~1.0.3", 734 | "parseurl": "~1.3.3", 735 | "send": "0.19.0" 736 | }, 737 | "engines": { 738 | "node": ">= 0.8.0" 739 | } 740 | }, 741 | "node_modules/setprototypeof": { 742 | "version": "1.2.0", 743 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 744 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 745 | "license": "ISC" 746 | }, 747 | "node_modules/side-channel": { 748 | "version": "1.1.0", 749 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 750 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 751 | "license": "MIT", 752 | "dependencies": { 753 | "es-errors": "^1.3.0", 754 | "object-inspect": "^1.13.3", 755 | "side-channel-list": "^1.0.0", 756 | "side-channel-map": "^1.0.1", 757 | "side-channel-weakmap": "^1.0.2" 758 | }, 759 | "engines": { 760 | "node": ">= 0.4" 761 | }, 762 | "funding": { 763 | "url": "https://github.com/sponsors/ljharb" 764 | } 765 | }, 766 | "node_modules/side-channel-list": { 767 | "version": "1.0.0", 768 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 769 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 770 | "license": "MIT", 771 | "dependencies": { 772 | "es-errors": "^1.3.0", 773 | "object-inspect": "^1.13.3" 774 | }, 775 | "engines": { 776 | "node": ">= 0.4" 777 | }, 778 | "funding": { 779 | "url": "https://github.com/sponsors/ljharb" 780 | } 781 | }, 782 | "node_modules/side-channel-map": { 783 | "version": "1.0.1", 784 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 785 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 786 | "license": "MIT", 787 | "dependencies": { 788 | "call-bound": "^1.0.2", 789 | "es-errors": "^1.3.0", 790 | "get-intrinsic": "^1.2.5", 791 | "object-inspect": "^1.13.3" 792 | }, 793 | "engines": { 794 | "node": ">= 0.4" 795 | }, 796 | "funding": { 797 | "url": "https://github.com/sponsors/ljharb" 798 | } 799 | }, 800 | "node_modules/side-channel-weakmap": { 801 | "version": "1.0.2", 802 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 803 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 804 | "license": "MIT", 805 | "dependencies": { 806 | "call-bound": "^1.0.2", 807 | "es-errors": "^1.3.0", 808 | "get-intrinsic": "^1.2.5", 809 | "object-inspect": "^1.13.3", 810 | "side-channel-map": "^1.0.1" 811 | }, 812 | "engines": { 813 | "node": ">= 0.4" 814 | }, 815 | "funding": { 816 | "url": "https://github.com/sponsors/ljharb" 817 | } 818 | }, 819 | "node_modules/statuses": { 820 | "version": "2.0.1", 821 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 822 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 823 | "license": "MIT", 824 | "engines": { 825 | "node": ">= 0.8" 826 | } 827 | }, 828 | "node_modules/toidentifier": { 829 | "version": "1.0.1", 830 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 831 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 832 | "license": "MIT", 833 | "engines": { 834 | "node": ">=0.6" 835 | } 836 | }, 837 | "node_modules/type-is": { 838 | "version": "1.6.18", 839 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 840 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 841 | "license": "MIT", 842 | "dependencies": { 843 | "media-typer": "0.3.0", 844 | "mime-types": "~2.1.24" 845 | }, 846 | "engines": { 847 | "node": ">= 0.6" 848 | } 849 | }, 850 | "node_modules/unpipe": { 851 | "version": "1.0.0", 852 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 853 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 854 | "license": "MIT", 855 | "engines": { 856 | "node": ">= 0.8" 857 | } 858 | }, 859 | "node_modules/url-parse": { 860 | "version": "1.5.10", 861 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 862 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 863 | "license": "MIT", 864 | "dependencies": { 865 | "querystringify": "^2.1.1", 866 | "requires-port": "^1.0.0" 867 | } 868 | }, 869 | "node_modules/utils-merge": { 870 | "version": "1.0.1", 871 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 872 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", 873 | "license": "MIT", 874 | "engines": { 875 | "node": ">= 0.4.0" 876 | } 877 | }, 878 | "node_modules/vary": { 879 | "version": "1.1.2", 880 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 881 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 882 | "license": "MIT", 883 | "engines": { 884 | "node": ">= 0.8" 885 | } 886 | } 887 | } 888 | } 889 | -------------------------------------------------------------------------------- /product-service/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "product-service", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "product-service", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "amqplib": "^0.10.8", 13 | "dotenv": "^17.2.0", 14 | "express": "^5.1.0", 15 | "joi": "^17.13.3", 16 | "mongoose": "^8.16.4" 17 | } 18 | }, 19 | "node_modules/@hapi/hoek": { 20 | "version": "9.3.0", 21 | "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", 22 | "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", 23 | "license": "BSD-3-Clause" 24 | }, 25 | "node_modules/@hapi/topo": { 26 | "version": "5.1.0", 27 | "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", 28 | "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", 29 | "license": "BSD-3-Clause", 30 | "dependencies": { 31 | "@hapi/hoek": "^9.0.0" 32 | } 33 | }, 34 | "node_modules/@mongodb-js/saslprep": { 35 | "version": "1.3.0", 36 | "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz", 37 | "integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==", 38 | "license": "MIT", 39 | "dependencies": { 40 | "sparse-bitfield": "^3.0.3" 41 | } 42 | }, 43 | "node_modules/@sideway/address": { 44 | "version": "4.1.5", 45 | "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", 46 | "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", 47 | "license": "BSD-3-Clause", 48 | "dependencies": { 49 | "@hapi/hoek": "^9.0.0" 50 | } 51 | }, 52 | "node_modules/@sideway/formula": { 53 | "version": "3.0.1", 54 | "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", 55 | "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", 56 | "license": "BSD-3-Clause" 57 | }, 58 | "node_modules/@sideway/pinpoint": { 59 | "version": "2.0.0", 60 | "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", 61 | "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", 62 | "license": "BSD-3-Clause" 63 | }, 64 | "node_modules/@types/webidl-conversions": { 65 | "version": "7.0.3", 66 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", 67 | "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", 68 | "license": "MIT" 69 | }, 70 | "node_modules/@types/whatwg-url": { 71 | "version": "11.0.5", 72 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", 73 | "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", 74 | "license": "MIT", 75 | "dependencies": { 76 | "@types/webidl-conversions": "*" 77 | } 78 | }, 79 | "node_modules/accepts": { 80 | "version": "2.0.0", 81 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 82 | "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 83 | "license": "MIT", 84 | "dependencies": { 85 | "mime-types": "^3.0.0", 86 | "negotiator": "^1.0.0" 87 | }, 88 | "engines": { 89 | "node": ">= 0.6" 90 | } 91 | }, 92 | "node_modules/amqplib": { 93 | "version": "0.10.8", 94 | "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.8.tgz", 95 | "integrity": "sha512-Tfn1O9sFgAP8DqeMEpt2IacsVTENBpblB3SqLdn0jK2AeX8iyCvbptBc8lyATT9bQ31MsjVwUSQ1g8f4jHOUfw==", 96 | "license": "MIT", 97 | "dependencies": { 98 | "buffer-more-ints": "~1.0.0", 99 | "url-parse": "~1.5.10" 100 | }, 101 | "engines": { 102 | "node": ">=10" 103 | } 104 | }, 105 | "node_modules/body-parser": { 106 | "version": "2.2.0", 107 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 108 | "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 109 | "license": "MIT", 110 | "dependencies": { 111 | "bytes": "^3.1.2", 112 | "content-type": "^1.0.5", 113 | "debug": "^4.4.0", 114 | "http-errors": "^2.0.0", 115 | "iconv-lite": "^0.6.3", 116 | "on-finished": "^2.4.1", 117 | "qs": "^6.14.0", 118 | "raw-body": "^3.0.0", 119 | "type-is": "^2.0.0" 120 | }, 121 | "engines": { 122 | "node": ">=18" 123 | } 124 | }, 125 | "node_modules/bson": { 126 | "version": "6.10.4", 127 | "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz", 128 | "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==", 129 | "license": "Apache-2.0", 130 | "engines": { 131 | "node": ">=16.20.1" 132 | } 133 | }, 134 | "node_modules/buffer-more-ints": { 135 | "version": "1.0.0", 136 | "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", 137 | "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==", 138 | "license": "MIT" 139 | }, 140 | "node_modules/bytes": { 141 | "version": "3.1.2", 142 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 143 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 144 | "license": "MIT", 145 | "engines": { 146 | "node": ">= 0.8" 147 | } 148 | }, 149 | "node_modules/call-bind-apply-helpers": { 150 | "version": "1.0.2", 151 | "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 152 | "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 153 | "license": "MIT", 154 | "dependencies": { 155 | "es-errors": "^1.3.0", 156 | "function-bind": "^1.1.2" 157 | }, 158 | "engines": { 159 | "node": ">= 0.4" 160 | } 161 | }, 162 | "node_modules/call-bound": { 163 | "version": "1.0.4", 164 | "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 165 | "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 166 | "license": "MIT", 167 | "dependencies": { 168 | "call-bind-apply-helpers": "^1.0.2", 169 | "get-intrinsic": "^1.3.0" 170 | }, 171 | "engines": { 172 | "node": ">= 0.4" 173 | }, 174 | "funding": { 175 | "url": "https://github.com/sponsors/ljharb" 176 | } 177 | }, 178 | "node_modules/content-disposition": { 179 | "version": "1.0.0", 180 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 181 | "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 182 | "license": "MIT", 183 | "dependencies": { 184 | "safe-buffer": "5.2.1" 185 | }, 186 | "engines": { 187 | "node": ">= 0.6" 188 | } 189 | }, 190 | "node_modules/content-type": { 191 | "version": "1.0.5", 192 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 193 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 194 | "license": "MIT", 195 | "engines": { 196 | "node": ">= 0.6" 197 | } 198 | }, 199 | "node_modules/cookie": { 200 | "version": "0.7.2", 201 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 202 | "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 203 | "license": "MIT", 204 | "engines": { 205 | "node": ">= 0.6" 206 | } 207 | }, 208 | "node_modules/cookie-signature": { 209 | "version": "1.2.2", 210 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 211 | "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 212 | "license": "MIT", 213 | "engines": { 214 | "node": ">=6.6.0" 215 | } 216 | }, 217 | "node_modules/debug": { 218 | "version": "4.4.1", 219 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 220 | "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 221 | "license": "MIT", 222 | "dependencies": { 223 | "ms": "^2.1.3" 224 | }, 225 | "engines": { 226 | "node": ">=6.0" 227 | }, 228 | "peerDependenciesMeta": { 229 | "supports-color": { 230 | "optional": true 231 | } 232 | } 233 | }, 234 | "node_modules/depd": { 235 | "version": "2.0.0", 236 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 237 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 238 | "license": "MIT", 239 | "engines": { 240 | "node": ">= 0.8" 241 | } 242 | }, 243 | "node_modules/dotenv": { 244 | "version": "17.2.0", 245 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.0.tgz", 246 | "integrity": "sha512-Q4sgBT60gzd0BB0lSyYD3xM4YxrXA9y4uBDof1JNYGzOXrQdQ6yX+7XIAqoFOGQFOTK1D3Hts5OllpxMDZFONQ==", 247 | "license": "BSD-2-Clause", 248 | "engines": { 249 | "node": ">=12" 250 | }, 251 | "funding": { 252 | "url": "https://dotenvx.com" 253 | } 254 | }, 255 | "node_modules/dunder-proto": { 256 | "version": "1.0.1", 257 | "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 258 | "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 259 | "license": "MIT", 260 | "dependencies": { 261 | "call-bind-apply-helpers": "^1.0.1", 262 | "es-errors": "^1.3.0", 263 | "gopd": "^1.2.0" 264 | }, 265 | "engines": { 266 | "node": ">= 0.4" 267 | } 268 | }, 269 | "node_modules/ee-first": { 270 | "version": "1.1.1", 271 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 272 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 273 | "license": "MIT" 274 | }, 275 | "node_modules/encodeurl": { 276 | "version": "2.0.0", 277 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 278 | "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 279 | "license": "MIT", 280 | "engines": { 281 | "node": ">= 0.8" 282 | } 283 | }, 284 | "node_modules/es-define-property": { 285 | "version": "1.0.1", 286 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 287 | "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 288 | "license": "MIT", 289 | "engines": { 290 | "node": ">= 0.4" 291 | } 292 | }, 293 | "node_modules/es-errors": { 294 | "version": "1.3.0", 295 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 296 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 297 | "license": "MIT", 298 | "engines": { 299 | "node": ">= 0.4" 300 | } 301 | }, 302 | "node_modules/es-object-atoms": { 303 | "version": "1.1.1", 304 | "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 305 | "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 306 | "license": "MIT", 307 | "dependencies": { 308 | "es-errors": "^1.3.0" 309 | }, 310 | "engines": { 311 | "node": ">= 0.4" 312 | } 313 | }, 314 | "node_modules/escape-html": { 315 | "version": "1.0.3", 316 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 317 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 318 | "license": "MIT" 319 | }, 320 | "node_modules/etag": { 321 | "version": "1.8.1", 322 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 323 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 324 | "license": "MIT", 325 | "engines": { 326 | "node": ">= 0.6" 327 | } 328 | }, 329 | "node_modules/express": { 330 | "version": "5.1.0", 331 | "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 332 | "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 333 | "license": "MIT", 334 | "dependencies": { 335 | "accepts": "^2.0.0", 336 | "body-parser": "^2.2.0", 337 | "content-disposition": "^1.0.0", 338 | "content-type": "^1.0.5", 339 | "cookie": "^0.7.1", 340 | "cookie-signature": "^1.2.1", 341 | "debug": "^4.4.0", 342 | "encodeurl": "^2.0.0", 343 | "escape-html": "^1.0.3", 344 | "etag": "^1.8.1", 345 | "finalhandler": "^2.1.0", 346 | "fresh": "^2.0.0", 347 | "http-errors": "^2.0.0", 348 | "merge-descriptors": "^2.0.0", 349 | "mime-types": "^3.0.0", 350 | "on-finished": "^2.4.1", 351 | "once": "^1.4.0", 352 | "parseurl": "^1.3.3", 353 | "proxy-addr": "^2.0.7", 354 | "qs": "^6.14.0", 355 | "range-parser": "^1.2.1", 356 | "router": "^2.2.0", 357 | "send": "^1.1.0", 358 | "serve-static": "^2.2.0", 359 | "statuses": "^2.0.1", 360 | "type-is": "^2.0.1", 361 | "vary": "^1.1.2" 362 | }, 363 | "engines": { 364 | "node": ">= 18" 365 | }, 366 | "funding": { 367 | "type": "opencollective", 368 | "url": "https://opencollective.com/express" 369 | } 370 | }, 371 | "node_modules/finalhandler": { 372 | "version": "2.1.0", 373 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 374 | "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 375 | "license": "MIT", 376 | "dependencies": { 377 | "debug": "^4.4.0", 378 | "encodeurl": "^2.0.0", 379 | "escape-html": "^1.0.3", 380 | "on-finished": "^2.4.1", 381 | "parseurl": "^1.3.3", 382 | "statuses": "^2.0.1" 383 | }, 384 | "engines": { 385 | "node": ">= 0.8" 386 | } 387 | }, 388 | "node_modules/forwarded": { 389 | "version": "0.2.0", 390 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 391 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 392 | "license": "MIT", 393 | "engines": { 394 | "node": ">= 0.6" 395 | } 396 | }, 397 | "node_modules/fresh": { 398 | "version": "2.0.0", 399 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 400 | "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 401 | "license": "MIT", 402 | "engines": { 403 | "node": ">= 0.8" 404 | } 405 | }, 406 | "node_modules/function-bind": { 407 | "version": "1.1.2", 408 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 409 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 410 | "license": "MIT", 411 | "funding": { 412 | "url": "https://github.com/sponsors/ljharb" 413 | } 414 | }, 415 | "node_modules/get-intrinsic": { 416 | "version": "1.3.0", 417 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 418 | "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 419 | "license": "MIT", 420 | "dependencies": { 421 | "call-bind-apply-helpers": "^1.0.2", 422 | "es-define-property": "^1.0.1", 423 | "es-errors": "^1.3.0", 424 | "es-object-atoms": "^1.1.1", 425 | "function-bind": "^1.1.2", 426 | "get-proto": "^1.0.1", 427 | "gopd": "^1.2.0", 428 | "has-symbols": "^1.1.0", 429 | "hasown": "^2.0.2", 430 | "math-intrinsics": "^1.1.0" 431 | }, 432 | "engines": { 433 | "node": ">= 0.4" 434 | }, 435 | "funding": { 436 | "url": "https://github.com/sponsors/ljharb" 437 | } 438 | }, 439 | "node_modules/get-proto": { 440 | "version": "1.0.1", 441 | "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 442 | "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 443 | "license": "MIT", 444 | "dependencies": { 445 | "dunder-proto": "^1.0.1", 446 | "es-object-atoms": "^1.0.0" 447 | }, 448 | "engines": { 449 | "node": ">= 0.4" 450 | } 451 | }, 452 | "node_modules/gopd": { 453 | "version": "1.2.0", 454 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 455 | "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 456 | "license": "MIT", 457 | "engines": { 458 | "node": ">= 0.4" 459 | }, 460 | "funding": { 461 | "url": "https://github.com/sponsors/ljharb" 462 | } 463 | }, 464 | "node_modules/has-symbols": { 465 | "version": "1.1.0", 466 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 467 | "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 468 | "license": "MIT", 469 | "engines": { 470 | "node": ">= 0.4" 471 | }, 472 | "funding": { 473 | "url": "https://github.com/sponsors/ljharb" 474 | } 475 | }, 476 | "node_modules/hasown": { 477 | "version": "2.0.2", 478 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 479 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 480 | "license": "MIT", 481 | "dependencies": { 482 | "function-bind": "^1.1.2" 483 | }, 484 | "engines": { 485 | "node": ">= 0.4" 486 | } 487 | }, 488 | "node_modules/http-errors": { 489 | "version": "2.0.0", 490 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 491 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 492 | "license": "MIT", 493 | "dependencies": { 494 | "depd": "2.0.0", 495 | "inherits": "2.0.4", 496 | "setprototypeof": "1.2.0", 497 | "statuses": "2.0.1", 498 | "toidentifier": "1.0.1" 499 | }, 500 | "engines": { 501 | "node": ">= 0.8" 502 | } 503 | }, 504 | "node_modules/http-errors/node_modules/statuses": { 505 | "version": "2.0.1", 506 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 507 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 508 | "license": "MIT", 509 | "engines": { 510 | "node": ">= 0.8" 511 | } 512 | }, 513 | "node_modules/iconv-lite": { 514 | "version": "0.6.3", 515 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 516 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 517 | "license": "MIT", 518 | "dependencies": { 519 | "safer-buffer": ">= 2.1.2 < 3.0.0" 520 | }, 521 | "engines": { 522 | "node": ">=0.10.0" 523 | } 524 | }, 525 | "node_modules/inherits": { 526 | "version": "2.0.4", 527 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 528 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 529 | "license": "ISC" 530 | }, 531 | "node_modules/ipaddr.js": { 532 | "version": "1.9.1", 533 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 534 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 535 | "license": "MIT", 536 | "engines": { 537 | "node": ">= 0.10" 538 | } 539 | }, 540 | "node_modules/is-promise": { 541 | "version": "4.0.0", 542 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 543 | "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 544 | "license": "MIT" 545 | }, 546 | "node_modules/joi": { 547 | "version": "17.13.3", 548 | "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", 549 | "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", 550 | "license": "BSD-3-Clause", 551 | "dependencies": { 552 | "@hapi/hoek": "^9.3.0", 553 | "@hapi/topo": "^5.1.0", 554 | "@sideway/address": "^4.1.5", 555 | "@sideway/formula": "^3.0.1", 556 | "@sideway/pinpoint": "^2.0.0" 557 | } 558 | }, 559 | "node_modules/kareem": { 560 | "version": "2.6.3", 561 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", 562 | "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", 563 | "license": "Apache-2.0", 564 | "engines": { 565 | "node": ">=12.0.0" 566 | } 567 | }, 568 | "node_modules/math-intrinsics": { 569 | "version": "1.1.0", 570 | "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 571 | "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 572 | "license": "MIT", 573 | "engines": { 574 | "node": ">= 0.4" 575 | } 576 | }, 577 | "node_modules/media-typer": { 578 | "version": "1.1.0", 579 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 580 | "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 581 | "license": "MIT", 582 | "engines": { 583 | "node": ">= 0.8" 584 | } 585 | }, 586 | "node_modules/memory-pager": { 587 | "version": "1.5.0", 588 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", 589 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", 590 | "license": "MIT" 591 | }, 592 | "node_modules/merge-descriptors": { 593 | "version": "2.0.0", 594 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 595 | "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 596 | "license": "MIT", 597 | "engines": { 598 | "node": ">=18" 599 | }, 600 | "funding": { 601 | "url": "https://github.com/sponsors/sindresorhus" 602 | } 603 | }, 604 | "node_modules/mime-db": { 605 | "version": "1.54.0", 606 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 607 | "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 608 | "license": "MIT", 609 | "engines": { 610 | "node": ">= 0.6" 611 | } 612 | }, 613 | "node_modules/mime-types": { 614 | "version": "3.0.1", 615 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 616 | "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 617 | "license": "MIT", 618 | "dependencies": { 619 | "mime-db": "^1.54.0" 620 | }, 621 | "engines": { 622 | "node": ">= 0.6" 623 | } 624 | }, 625 | "node_modules/mongodb": { 626 | "version": "6.17.0", 627 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.17.0.tgz", 628 | "integrity": "sha512-neerUzg/8U26cgruLysKEjJvoNSXhyID3RvzvdcpsIi2COYM3FS3o9nlH7fxFtefTb942dX3W9i37oPfCVj4wA==", 629 | "license": "Apache-2.0", 630 | "dependencies": { 631 | "@mongodb-js/saslprep": "^1.1.9", 632 | "bson": "^6.10.4", 633 | "mongodb-connection-string-url": "^3.0.0" 634 | }, 635 | "engines": { 636 | "node": ">=16.20.1" 637 | }, 638 | "peerDependencies": { 639 | "@aws-sdk/credential-providers": "^3.188.0", 640 | "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", 641 | "gcp-metadata": "^5.2.0", 642 | "kerberos": "^2.0.1", 643 | "mongodb-client-encryption": ">=6.0.0 <7", 644 | "snappy": "^7.2.2", 645 | "socks": "^2.7.1" 646 | }, 647 | "peerDependenciesMeta": { 648 | "@aws-sdk/credential-providers": { 649 | "optional": true 650 | }, 651 | "@mongodb-js/zstd": { 652 | "optional": true 653 | }, 654 | "gcp-metadata": { 655 | "optional": true 656 | }, 657 | "kerberos": { 658 | "optional": true 659 | }, 660 | "mongodb-client-encryption": { 661 | "optional": true 662 | }, 663 | "snappy": { 664 | "optional": true 665 | }, 666 | "socks": { 667 | "optional": true 668 | } 669 | } 670 | }, 671 | "node_modules/mongodb-connection-string-url": { 672 | "version": "3.0.2", 673 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", 674 | "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", 675 | "license": "Apache-2.0", 676 | "dependencies": { 677 | "@types/whatwg-url": "^11.0.2", 678 | "whatwg-url": "^14.1.0 || ^13.0.0" 679 | } 680 | }, 681 | "node_modules/mongoose": { 682 | "version": "8.16.4", 683 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.16.4.tgz", 684 | "integrity": "sha512-jslgdQ8pY2vcNSKPv3Dbi5ogo/NT8zcvf6kPDyD8Sdsjsa1at3AFAF0F5PT+jySPGSPbvlNaQ49nT9h+Kx2UDA==", 685 | "license": "MIT", 686 | "dependencies": { 687 | "bson": "^6.10.4", 688 | "kareem": "2.6.3", 689 | "mongodb": "~6.17.0", 690 | "mpath": "0.9.0", 691 | "mquery": "5.0.0", 692 | "ms": "2.1.3", 693 | "sift": "17.1.3" 694 | }, 695 | "engines": { 696 | "node": ">=16.20.1" 697 | }, 698 | "funding": { 699 | "type": "opencollective", 700 | "url": "https://opencollective.com/mongoose" 701 | } 702 | }, 703 | "node_modules/mpath": { 704 | "version": "0.9.0", 705 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", 706 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", 707 | "license": "MIT", 708 | "engines": { 709 | "node": ">=4.0.0" 710 | } 711 | }, 712 | "node_modules/mquery": { 713 | "version": "5.0.0", 714 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", 715 | "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", 716 | "license": "MIT", 717 | "dependencies": { 718 | "debug": "4.x" 719 | }, 720 | "engines": { 721 | "node": ">=14.0.0" 722 | } 723 | }, 724 | "node_modules/ms": { 725 | "version": "2.1.3", 726 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 727 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 728 | "license": "MIT" 729 | }, 730 | "node_modules/negotiator": { 731 | "version": "1.0.0", 732 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 733 | "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 734 | "license": "MIT", 735 | "engines": { 736 | "node": ">= 0.6" 737 | } 738 | }, 739 | "node_modules/object-inspect": { 740 | "version": "1.13.4", 741 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 742 | "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 743 | "license": "MIT", 744 | "engines": { 745 | "node": ">= 0.4" 746 | }, 747 | "funding": { 748 | "url": "https://github.com/sponsors/ljharb" 749 | } 750 | }, 751 | "node_modules/on-finished": { 752 | "version": "2.4.1", 753 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 754 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 755 | "license": "MIT", 756 | "dependencies": { 757 | "ee-first": "1.1.1" 758 | }, 759 | "engines": { 760 | "node": ">= 0.8" 761 | } 762 | }, 763 | "node_modules/once": { 764 | "version": "1.4.0", 765 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 766 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 767 | "license": "ISC", 768 | "dependencies": { 769 | "wrappy": "1" 770 | } 771 | }, 772 | "node_modules/parseurl": { 773 | "version": "1.3.3", 774 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 775 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 776 | "license": "MIT", 777 | "engines": { 778 | "node": ">= 0.8" 779 | } 780 | }, 781 | "node_modules/path-to-regexp": { 782 | "version": "8.2.0", 783 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 784 | "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 785 | "license": "MIT", 786 | "engines": { 787 | "node": ">=16" 788 | } 789 | }, 790 | "node_modules/proxy-addr": { 791 | "version": "2.0.7", 792 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 793 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 794 | "license": "MIT", 795 | "dependencies": { 796 | "forwarded": "0.2.0", 797 | "ipaddr.js": "1.9.1" 798 | }, 799 | "engines": { 800 | "node": ">= 0.10" 801 | } 802 | }, 803 | "node_modules/punycode": { 804 | "version": "2.3.1", 805 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 806 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 807 | "license": "MIT", 808 | "engines": { 809 | "node": ">=6" 810 | } 811 | }, 812 | "node_modules/qs": { 813 | "version": "6.14.0", 814 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 815 | "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 816 | "license": "BSD-3-Clause", 817 | "dependencies": { 818 | "side-channel": "^1.1.0" 819 | }, 820 | "engines": { 821 | "node": ">=0.6" 822 | }, 823 | "funding": { 824 | "url": "https://github.com/sponsors/ljharb" 825 | } 826 | }, 827 | "node_modules/querystringify": { 828 | "version": "2.2.0", 829 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", 830 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", 831 | "license": "MIT" 832 | }, 833 | "node_modules/range-parser": { 834 | "version": "1.2.1", 835 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 836 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 837 | "license": "MIT", 838 | "engines": { 839 | "node": ">= 0.6" 840 | } 841 | }, 842 | "node_modules/raw-body": { 843 | "version": "3.0.0", 844 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 845 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 846 | "license": "MIT", 847 | "dependencies": { 848 | "bytes": "3.1.2", 849 | "http-errors": "2.0.0", 850 | "iconv-lite": "0.6.3", 851 | "unpipe": "1.0.0" 852 | }, 853 | "engines": { 854 | "node": ">= 0.8" 855 | } 856 | }, 857 | "node_modules/requires-port": { 858 | "version": "1.0.0", 859 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", 860 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", 861 | "license": "MIT" 862 | }, 863 | "node_modules/router": { 864 | "version": "2.2.0", 865 | "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 866 | "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 867 | "license": "MIT", 868 | "dependencies": { 869 | "debug": "^4.4.0", 870 | "depd": "^2.0.0", 871 | "is-promise": "^4.0.0", 872 | "parseurl": "^1.3.3", 873 | "path-to-regexp": "^8.0.0" 874 | }, 875 | "engines": { 876 | "node": ">= 18" 877 | } 878 | }, 879 | "node_modules/safe-buffer": { 880 | "version": "5.2.1", 881 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 882 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 883 | "funding": [ 884 | { 885 | "type": "github", 886 | "url": "https://github.com/sponsors/feross" 887 | }, 888 | { 889 | "type": "patreon", 890 | "url": "https://www.patreon.com/feross" 891 | }, 892 | { 893 | "type": "consulting", 894 | "url": "https://feross.org/support" 895 | } 896 | ], 897 | "license": "MIT" 898 | }, 899 | "node_modules/safer-buffer": { 900 | "version": "2.1.2", 901 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 902 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 903 | "license": "MIT" 904 | }, 905 | "node_modules/send": { 906 | "version": "1.2.0", 907 | "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 908 | "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 909 | "license": "MIT", 910 | "dependencies": { 911 | "debug": "^4.3.5", 912 | "encodeurl": "^2.0.0", 913 | "escape-html": "^1.0.3", 914 | "etag": "^1.8.1", 915 | "fresh": "^2.0.0", 916 | "http-errors": "^2.0.0", 917 | "mime-types": "^3.0.1", 918 | "ms": "^2.1.3", 919 | "on-finished": "^2.4.1", 920 | "range-parser": "^1.2.1", 921 | "statuses": "^2.0.1" 922 | }, 923 | "engines": { 924 | "node": ">= 18" 925 | } 926 | }, 927 | "node_modules/serve-static": { 928 | "version": "2.2.0", 929 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 930 | "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 931 | "license": "MIT", 932 | "dependencies": { 933 | "encodeurl": "^2.0.0", 934 | "escape-html": "^1.0.3", 935 | "parseurl": "^1.3.3", 936 | "send": "^1.2.0" 937 | }, 938 | "engines": { 939 | "node": ">= 18" 940 | } 941 | }, 942 | "node_modules/setprototypeof": { 943 | "version": "1.2.0", 944 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 945 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 946 | "license": "ISC" 947 | }, 948 | "node_modules/side-channel": { 949 | "version": "1.1.0", 950 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 951 | "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 952 | "license": "MIT", 953 | "dependencies": { 954 | "es-errors": "^1.3.0", 955 | "object-inspect": "^1.13.3", 956 | "side-channel-list": "^1.0.0", 957 | "side-channel-map": "^1.0.1", 958 | "side-channel-weakmap": "^1.0.2" 959 | }, 960 | "engines": { 961 | "node": ">= 0.4" 962 | }, 963 | "funding": { 964 | "url": "https://github.com/sponsors/ljharb" 965 | } 966 | }, 967 | "node_modules/side-channel-list": { 968 | "version": "1.0.0", 969 | "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 970 | "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 971 | "license": "MIT", 972 | "dependencies": { 973 | "es-errors": "^1.3.0", 974 | "object-inspect": "^1.13.3" 975 | }, 976 | "engines": { 977 | "node": ">= 0.4" 978 | }, 979 | "funding": { 980 | "url": "https://github.com/sponsors/ljharb" 981 | } 982 | }, 983 | "node_modules/side-channel-map": { 984 | "version": "1.0.1", 985 | "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 986 | "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 987 | "license": "MIT", 988 | "dependencies": { 989 | "call-bound": "^1.0.2", 990 | "es-errors": "^1.3.0", 991 | "get-intrinsic": "^1.2.5", 992 | "object-inspect": "^1.13.3" 993 | }, 994 | "engines": { 995 | "node": ">= 0.4" 996 | }, 997 | "funding": { 998 | "url": "https://github.com/sponsors/ljharb" 999 | } 1000 | }, 1001 | "node_modules/side-channel-weakmap": { 1002 | "version": "1.0.2", 1003 | "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1004 | "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1005 | "license": "MIT", 1006 | "dependencies": { 1007 | "call-bound": "^1.0.2", 1008 | "es-errors": "^1.3.0", 1009 | "get-intrinsic": "^1.2.5", 1010 | "object-inspect": "^1.13.3", 1011 | "side-channel-map": "^1.0.1" 1012 | }, 1013 | "engines": { 1014 | "node": ">= 0.4" 1015 | }, 1016 | "funding": { 1017 | "url": "https://github.com/sponsors/ljharb" 1018 | } 1019 | }, 1020 | "node_modules/sift": { 1021 | "version": "17.1.3", 1022 | "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", 1023 | "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", 1024 | "license": "MIT" 1025 | }, 1026 | "node_modules/sparse-bitfield": { 1027 | "version": "3.0.3", 1028 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", 1029 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", 1030 | "license": "MIT", 1031 | "dependencies": { 1032 | "memory-pager": "^1.0.2" 1033 | } 1034 | }, 1035 | "node_modules/statuses": { 1036 | "version": "2.0.2", 1037 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", 1038 | "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", 1039 | "license": "MIT", 1040 | "engines": { 1041 | "node": ">= 0.8" 1042 | } 1043 | }, 1044 | "node_modules/toidentifier": { 1045 | "version": "1.0.1", 1046 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1047 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1048 | "license": "MIT", 1049 | "engines": { 1050 | "node": ">=0.6" 1051 | } 1052 | }, 1053 | "node_modules/tr46": { 1054 | "version": "5.1.1", 1055 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", 1056 | "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", 1057 | "license": "MIT", 1058 | "dependencies": { 1059 | "punycode": "^2.3.1" 1060 | }, 1061 | "engines": { 1062 | "node": ">=18" 1063 | } 1064 | }, 1065 | "node_modules/type-is": { 1066 | "version": "2.0.1", 1067 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 1068 | "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 1069 | "license": "MIT", 1070 | "dependencies": { 1071 | "content-type": "^1.0.5", 1072 | "media-typer": "^1.1.0", 1073 | "mime-types": "^3.0.0" 1074 | }, 1075 | "engines": { 1076 | "node": ">= 0.6" 1077 | } 1078 | }, 1079 | "node_modules/unpipe": { 1080 | "version": "1.0.0", 1081 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1082 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1083 | "license": "MIT", 1084 | "engines": { 1085 | "node": ">= 0.8" 1086 | } 1087 | }, 1088 | "node_modules/url-parse": { 1089 | "version": "1.5.10", 1090 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", 1091 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", 1092 | "license": "MIT", 1093 | "dependencies": { 1094 | "querystringify": "^2.1.1", 1095 | "requires-port": "^1.0.0" 1096 | } 1097 | }, 1098 | "node_modules/vary": { 1099 | "version": "1.1.2", 1100 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1101 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1102 | "license": "MIT", 1103 | "engines": { 1104 | "node": ">= 0.8" 1105 | } 1106 | }, 1107 | "node_modules/webidl-conversions": { 1108 | "version": "7.0.0", 1109 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", 1110 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", 1111 | "license": "BSD-2-Clause", 1112 | "engines": { 1113 | "node": ">=12" 1114 | } 1115 | }, 1116 | "node_modules/whatwg-url": { 1117 | "version": "14.2.0", 1118 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", 1119 | "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", 1120 | "license": "MIT", 1121 | "dependencies": { 1122 | "tr46": "^5.1.0", 1123 | "webidl-conversions": "^7.0.0" 1124 | }, 1125 | "engines": { 1126 | "node": ">=18" 1127 | } 1128 | }, 1129 | "node_modules/wrappy": { 1130 | "version": "1.0.2", 1131 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1132 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1133 | "license": "ISC" 1134 | } 1135 | } 1136 | } 1137 | --------------------------------------------------------------------------------