├── buy-api
├── .dockerignore
├── RouteErrorHandler.js
├── Dockerfile
├── Routes
│ ├── healthCheckRoutes.js
│ └── buyRoutes.js
├── package.json
├── index.js
├── Queue
│ ├── Queue.js
│ └── RabbitMQ.js
└── package-lock.json
├── buy-job
├── .dockerignore
├── Dockerfile
├── package.json
├── Event
│ ├── Event.js
│ └── Kafka.js
├── index.js
├── Queue
│ ├── Queue.js
│ └── RabbitMQ.js
├── consumer.js
├── Routes
│ └── healthCheckRoutes.js
└── package-lock.json
├── product-api
├── .dockerignore
├── RouteErrorHandler.js
├── Dockerfile
├── package.json
├── Database
│ ├── ProductModel.js
│ └── Database.js
├── index.js
├── Routes
│ ├── productRoutesWithoutCache.js
│ ├── healthCheckRoutes.js
│ └── productRoutes.js
└── Cache
│ └── Cache.js
├── front-end
├── .dockerignore
├── babel.config.js
├── .env.production
├── .env.development
├── src
│ ├── main.js
│ ├── App.vue
│ └── components
│ │ ├── DialogBox.vue
│ │ └── ProductList.vue
├── vue.config.js
├── jsconfig.json
├── README.md
├── public
│ └── index.html
├── Dockerfile
├── package.json
├── ssl
│ ├── dev.crt
│ └── dev.key
└── nginx.conf
├── ecommerce-lab.jpg
├── apache-benchmark-test
├── Dockerfile
├── ab-product-api-local.sh
└── README.md
├── .gitignore
├── gitops
├── databases-helm
│ ├── rabbitmq
│ │ └── rabbitmq.yml
│ ├── redis
│ │ └── redis.yml
│ ├── kafka-ui
│ │ └── kafka-ui.yml
│ ├── mongodb
│ │ ├── mongodb.yml
│ │ └── mongodb-configmap.yml
│ └── kafka
│ │ └── kafka.yml
├── apps
│ ├── front-end
│ │ └── front-end.yml
│ ├── buy-job
│ │ └── buy-job.yml
│ ├── product-api
│ │ └── product-api.yml
│ └── buy-api
│ │ └── buy-api.yml
├── ArgoCD-apps.yml
└── databases
│ ├── redis.yml
│ ├── rabbitmq.yml
│ └── mongodb.yml
├── mongodb
└── mongo-init.js
├── docker-compose.yml
├── docker-compose-dev.yml
├── README.md
└── ecommerce-lab.drawio
/buy-api/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/buy-job/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/product-api/.dockerignore:
--------------------------------------------------------------------------------
1 | .env*
2 | node_modules
--------------------------------------------------------------------------------
/front-end/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 |
--------------------------------------------------------------------------------
/ecommerce-lab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gustavoapolinario/eccomerce-lab/master/ecommerce-lab.jpg
--------------------------------------------------------------------------------
/front-end/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/front-end/.env.production:
--------------------------------------------------------------------------------
1 | VUE_APP_PRODUCT_API_URL=http://localhost:3000/
2 | VUE_APP_BUY_API_URL=http://localhost:3001/
3 |
--------------------------------------------------------------------------------
/front-end/.env.development:
--------------------------------------------------------------------------------
1 | VUE_APP_PRODUCT_API_URL=http://localhost:3000/
2 | VUE_APP_BUY_API_URL=http://localhost:3001/
3 |
--------------------------------------------------------------------------------
/front-end/src/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue'
2 | import App from './App.vue'
3 |
4 | createApp(App).mount('#app')
5 |
--------------------------------------------------------------------------------
/apache-benchmark-test/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:latest
2 |
3 | RUN apt-get update && apt-get install -y apache2-utils
4 |
5 | #ENTRYPOINT [ "/bin/sh", "ab" ]
6 | CMD ["ab", "-h"]
7 |
--------------------------------------------------------------------------------
/buy-api/RouteErrorHandler.js:
--------------------------------------------------------------------------------
1 | const RouteErrorHandler = (fn) => (req, res, next) => {
2 | Promise.resolve(fn(req, res, next)).catch(next);
3 | };
4 |
5 | module.exports = RouteErrorHandler;
6 |
--------------------------------------------------------------------------------
/product-api/RouteErrorHandler.js:
--------------------------------------------------------------------------------
1 | const RouteErrorHandler = (fn) => (req, res, next) => {
2 | Promise.resolve(fn(req, res, next)).catch(next);
3 | };
4 |
5 | module.exports = RouteErrorHandler;
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/.DS_Store
2 | **/node_modules
3 | **/dist
4 |
5 |
6 | # local env files
7 | **/.env.local
8 | **/.env.*.local
9 |
10 | # Log files
11 | **/npm-debug.log*
12 | **/yarn-debug.log*
13 | **/yarn-error.log*
14 | **/pnpm-debug.log*
15 |
--------------------------------------------------------------------------------
/apache-benchmark-test/ab-product-api-local.sh:
--------------------------------------------------------------------------------
1 | url=$1
2 | if [ "$url" == "" ];then
3 | url="/products"
4 | fi
5 | docker_ip=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' ecommerce-lab-product-api-1)
6 | docker run --network=ecommerce-lab_default --rm apache-benchmark ab -n 100 -c 10 http://$docker_ip:3000$url
7 |
--------------------------------------------------------------------------------
/buy-api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20
2 |
3 | RUN groupadd -g 1001 appuser && useradd -u 1001 -g appuser -s /bin/sh -m appuser
4 |
5 | WORKDIR /app
6 |
7 | RUN chown -R appuser:appuser /app
8 |
9 | USER 1001
10 |
11 | COPY package*.json ./
12 | RUN npm ci --omit dev
13 |
14 | COPY . .
15 |
16 | EXPOSE 3000
17 |
18 | CMD [ "node", "index.js" ]
19 |
--------------------------------------------------------------------------------
/buy-job/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20
2 |
3 | RUN groupadd -g 1001 appuser && useradd -u 1001 -g appuser -s /bin/sh -m appuser
4 |
5 | WORKDIR /app
6 |
7 | RUN chown -R appuser:appuser /app
8 |
9 | USER 1001
10 |
11 | COPY package*.json ./
12 | RUN npm ci --omit dev
13 |
14 | COPY . .
15 |
16 | EXPOSE 3000
17 |
18 | CMD [ "node", "index.js" ]
19 |
--------------------------------------------------------------------------------
/front-end/vue.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('@vue/cli-service')
2 | module.exports = defineConfig({
3 | transpileDependencies: true,
4 | devServer: {
5 | host: '0.0.0.0',
6 | port: process.env.PORT || 8080,
7 | allowedHosts: [
8 | 'npm-container' // My Dev Environment is inside a container
9 | ]
10 | }
11 | })
12 |
--------------------------------------------------------------------------------
/product-api/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:20
2 |
3 | RUN groupadd -g 1001 appuser && useradd -u 1001 -g appuser -s /bin/sh -m appuser
4 |
5 | WORKDIR /app
6 |
7 | RUN chown -R appuser:appuser /app
8 |
9 | USER 1001
10 |
11 | COPY package*.json ./
12 | RUN npm ci --omit dev
13 |
14 | COPY . .
15 |
16 | EXPOSE 3000
17 |
18 | CMD [ "node", "index.js" ]
19 |
--------------------------------------------------------------------------------
/front-end/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "baseUrl": "./",
6 | "moduleResolution": "node",
7 | "paths": {
8 | "@/*": [
9 | "src/*"
10 | ]
11 | },
12 | "lib": [
13 | "esnext",
14 | "dom",
15 | "dom.iterable",
16 | "scripthost"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/front-end/README.md:
--------------------------------------------------------------------------------
1 | # front-end
2 |
3 | ## Project setup
4 | ```
5 | npm install
6 | ```
7 |
8 | ### Compiles and hot-reloads for development
9 | ```
10 | npm run serve
11 | ```
12 |
13 | ### Compiles and minifies for production
14 | ```
15 | npm run build
16 | ```
17 |
18 | ### Lints and fixes files
19 | ```
20 | npm run lint
21 | ```
22 |
23 | ### Customize configuration
24 | See [Configuration Reference](https://cli.vuejs.org/config/).
25 |
--------------------------------------------------------------------------------
/buy-job/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "buy-job",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "start": "node index.js",
7 | "dev": "NODE_ENV=development nodemon index.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "description": "",
13 | "dependencies": {
14 | "amqplib": "^0.10.4",
15 | "express": "^4.19.2",
16 | "kafkajs": "^2.2.4"
17 | },
18 | "devDependencies": {
19 | "nodemon": "^3.1.3"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/buy-api/Routes/healthCheckRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | /**
5 | * Health check to be used on Liveness Probes health check
6 | */
7 | router.get('/healthcheck', async (req, res) => {
8 | try {
9 | res.status(200).json({ status: 'healthy' });
10 | } catch (err) {
11 | console.error('Health check failed:', err);
12 | res.status(500).json({ status: 'unhealthy', error: err.message });
13 | }
14 | });
15 |
16 | module.exports = router;
17 |
--------------------------------------------------------------------------------
/front-end/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
17 |
18 |
30 |
--------------------------------------------------------------------------------
/product-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "product-api",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "start": "node index.js",
7 | "dev": "NODE_ENV=development nodemon index.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "description": "",
13 | "dependencies": {
14 | "cors": "^2.8.5",
15 | "express": "^4.19.2",
16 | "mongoose": "^8.4.1",
17 | "redis": "^4.6.14"
18 | },
19 | "devDependencies": {
20 | "nodemon": "^3.1.3"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/buy-job/Event/Event.js:
--------------------------------------------------------------------------------
1 | const Kafka = require('./Kafka');
2 |
3 |
4 | class Event {
5 | constructor() {
6 | this.kafka = new Kafka();
7 | }
8 |
9 | async createTopic(topic) {
10 | await this.kafka.createTopic(topic)
11 | }
12 |
13 | async connect() {
14 | await this.kafka.connect()
15 | }
16 |
17 | async sendMessage(topic, messages) {
18 | await this.kafka.sendMessage(topic, messages)
19 | }
20 |
21 | async disconnect() {
22 | await this.kafka.disconnect()
23 | }
24 | }
25 |
26 | module.exports = Event;
27 |
--------------------------------------------------------------------------------
/buy-api/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "buy-api",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "scripts": {
6 | "start": "node index.js",
7 | "dev": "NODE_ENV=development nodemon index.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "description": "",
13 | "dependencies": {
14 | "amqplib": "^0.10.4",
15 | "axios": "^1.7.2",
16 | "cors": "^2.8.5",
17 | "express": "^4.19.2",
18 | "uuid": "^9.0.1"
19 | },
20 | "devDependencies": {
21 | "nodemon": "^3.1.3"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/product-api/Database/ProductModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const productSchema = new mongoose.Schema({
4 | name: { type: String, required: true },
5 | description: { type: String, required: false },
6 | image: { type: String, required: true },
7 | price: { type: Number, min: 1, max: 1000, required: true },
8 | createdAt: { type: Date },
9 | updatedAt: { type: Date }
10 | }, {
11 | timestamps: {
12 | createdAt: 'createdAt',
13 | updatedAt: 'updatedAt'
14 | }
15 | })
16 |
17 | const ProductModel = mongoose.model('Product', productSchema);
18 |
19 | module.exports = ProductModel
20 |
--------------------------------------------------------------------------------
/buy-job/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const healthcheck = require('./Routes/healthCheckRoutes');
3 | const Consumer = require('./consumer');
4 | const consumer = new Consumer()
5 |
6 | const app = express();
7 | const port = process.env.PORT || 3000;
8 |
9 | app.use(express.json());
10 |
11 | // Use routes
12 | app.use(healthcheck(consumer));
13 |
14 | consumer.createConsumer();
15 |
16 | // Error handling middleware
17 | app.use((err, req, res, next) => {
18 | console.error(err);
19 | res.status(500).json({ error: 'Internal server error' });
20 | });
21 |
22 | app.listen(port, () => {
23 | console.log(`Server running at http://localhost:${port}`);
24 | });
25 |
--------------------------------------------------------------------------------
/buy-api/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const buyRoutes = require('./Routes/buyRoutes');
4 | const healthcheck = require('./Routes/healthCheckRoutes');
5 |
6 | const app = express();
7 | const port = process.env.PORT || 3000;
8 |
9 | app.use(cors());
10 | app.use(express.json());
11 |
12 | // Use routes
13 | app.use(buyRoutes);
14 | app.use(healthcheck);
15 |
16 | // Error handling middleware
17 | app.use((err, req, res, next) => {
18 | console.error(err);
19 | res.status(500).json({ error: 'Internal server error' });
20 | });
21 |
22 | app.listen(port, () => {
23 | console.log(`Server running at http://localhost:${port}`);
24 | });
25 |
--------------------------------------------------------------------------------
/front-end/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/gitops/databases-helm/rabbitmq/rabbitmq.yml:
--------------------------------------------------------------------------------
1 | apiVersion: argoproj.io/v1alpha1
2 | kind: Application
3 | metadata:
4 | name: eccomerce-lab-rabbitmq
5 | namespace: argocd
6 | spec:
7 | destination:
8 | server: 'https://kubernetes.default.svc'
9 | namespace: default
10 | project: default
11 | source:
12 | chart: rabbitmq
13 | repoURL: https://charts.bitnami.com/bitnami
14 | targetRevision: 14.4.4
15 | helm:
16 | parameters:
17 | - name: auth.username
18 | value: rabbitmq
19 | - name: auth.password
20 | value: rabbitmq-password
21 | - name: persistence.size
22 | value: 1Gi
23 | syncPolicy:
24 | automated: {}
25 |
--------------------------------------------------------------------------------
/apache-benchmark-test/README.md:
--------------------------------------------------------------------------------
1 | # About
2 |
3 | Apache Benchmark Test is a tool who makes a benchmark to test the time spend on requests.
4 | it makes number of request simultaneously to a endpoint. When one of the request ends, it start another until the number of total requests configured.
5 |
6 | ex:
7 | ab -n 100 -c 10 http://localhost:3000/products
8 |
9 | this command will make 10 simultaneously requests and making new one until finish all 100 requests.
10 |
11 |
12 | # Benchmark test
13 |
14 | Let's test our project with and without cache
15 |
16 | with Redis as cache:
17 |
18 | ./ab-product-api-local.sh /products
19 |
20 | Without Redis as cache:
21 |
22 | ./ab-product-api-local.sh /products-without-cache
23 |
--------------------------------------------------------------------------------
/gitops/databases-helm/redis/redis.yml:
--------------------------------------------------------------------------------
1 | apiVersion: argoproj.io/v1alpha1
2 | kind: Application
3 | metadata:
4 | name: eccomerce-lab-redis
5 | namespace: argocd
6 | spec:
7 | destination:
8 | server: 'https://kubernetes.default.svc'
9 | namespace: default
10 | project: default
11 | source:
12 | chart: redis
13 | repoURL: https://charts.bitnami.com/bitnami
14 | targetRevision: 19.5.5
15 | helm:
16 | parameters:
17 | - name: global.redis.password
18 | value: your_redis_password
19 | - name: master.persistence.size
20 | value: 300Mi
21 | - name: replica.persistence.size
22 | value: 300Mi
23 | - name: replica.replicaCount
24 | value: "1"
25 | syncPolicy:
26 | automated: {}
27 |
28 |
--------------------------------------------------------------------------------
/product-api/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const productRoutes = require('./Routes/productRoutes');
4 | const productRoutesWithoutCache = require('./Routes/productRoutesWithoutCache');
5 | const healthcheck = require('./Routes/healthCheckRoutes');
6 |
7 | const app = express();
8 | const port = process.env.PORT || 3000;
9 |
10 | app.use(cors());
11 | app.use(express.json());
12 |
13 | // Use routes
14 | app.use(productRoutes);
15 | app.use(productRoutesWithoutCache);
16 | app.use(healthcheck);
17 |
18 | // Error handling middleware
19 | app.use((err, req, res, next) => {
20 | console.error(err);
21 | res.status(500).json({ error: 'Internal server error' });
22 | });
23 |
24 | app.listen(port, () => {
25 | console.log(`Server running at http://localhost:${port}`);
26 | });
27 |
--------------------------------------------------------------------------------
/gitops/databases-helm/kafka-ui/kafka-ui.yml:
--------------------------------------------------------------------------------
1 | apiVersion: argoproj.io/v1alpha1
2 | kind: Application
3 | metadata:
4 | name: eccomerce-lab-kafka-ui
5 | namespace: argocd
6 | spec:
7 | destination:
8 | server: 'https://kubernetes.default.svc'
9 | namespace: default
10 | project: default
11 | source:
12 | chart: kafka-ui
13 | repoURL: https://provectus.github.io/kafka-ui-charts
14 | targetRevision: 0.7.6
15 | helm:
16 | values: |
17 | yamlApplicationConfig:
18 | kafka:
19 | clusters:
20 | - name: kafka
21 | bootstrapServers: eccomerce-lab-kafka:9092
22 | auth:
23 | type: disabled
24 | management:
25 | health:
26 | ldap:
27 | enabled: false
28 | syncPolicy:
29 | automated: {}
30 |
--------------------------------------------------------------------------------
/gitops/databases-helm/mongodb/mongodb.yml:
--------------------------------------------------------------------------------
1 | apiVersion: argoproj.io/v1alpha1
2 | kind: Application
3 | metadata:
4 | name: eccomerce-lab-mongodb
5 | namespace: argocd
6 | spec:
7 | destination:
8 | server: 'https://kubernetes.default.svc'
9 | namespace: default
10 | project: default
11 | source:
12 | chart: mongodb
13 | repoURL: https://charts.bitnami.com/bitnami
14 | targetRevision: 15.6.9
15 | helm:
16 | parameters:
17 | - name: auth.username
18 | value: product-api
19 | - name: auth.password
20 | value: product-api-password
21 | - name: auth.database
22 | value: products
23 | - name: persistence.size
24 | value: 1Gi
25 | - name: initdbScriptsConfigMap
26 | value: mongo-init-configmap
27 | syncPolicy:
28 | automated: {}
29 |
30 |
--------------------------------------------------------------------------------
/product-api/Routes/productRoutesWithoutCache.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const Database = require('../Database/Database');
3 | const RouteErrorHandler = require('../RouteErrorHandler');
4 |
5 | const router = express.Router();
6 | const db = new Database();
7 |
8 | // Get all products without caching
9 | router.get('/products-without-cache', RouteErrorHandler(async (req, res) => {
10 | const products = await db.getProducts();
11 | res.status(200).json(products);
12 | }));
13 |
14 | router.get('/products-without-cache/:id', RouteErrorHandler(async (req, res) => {
15 | const productId = req.params.id
16 | const product = await db.getProductById(productId);
17 | if (product) {
18 | res.json(product);
19 | } else {
20 | res.status(404).json({ message: 'Product not found' });
21 | }
22 | }));
23 |
24 | module.exports = router;
25 |
--------------------------------------------------------------------------------
/gitops/databases-helm/kafka/kafka.yml:
--------------------------------------------------------------------------------
1 | apiVersion: argoproj.io/v1alpha1
2 | kind: Application
3 | metadata:
4 | name: eccomerce-lab-kafka
5 | namespace: argocd
6 | spec:
7 | destination:
8 | server: 'https://kubernetes.default.svc'
9 | namespace: default
10 | project: default
11 | source:
12 | chart: kafka
13 | repoURL: https://charts.bitnami.com/bitnami
14 | targetRevision: 29.3.4
15 | helm:
16 | parameters:
17 | - name: auth.enabled
18 | value: "false"
19 | - name: controller.persistence.size
20 | value: 1Gi
21 | - name: zookeeper.persistence.size
22 | value: 1Gi
23 | - name: controller.replicaCount
24 | value: "1"
25 | - name: zookeeper.replicaCount
26 | value: "1"
27 | - name: listeners.client.protocol
28 | value: PLAINTEXT
29 | syncPolicy:
30 | automated: {}
31 |
--------------------------------------------------------------------------------
/front-end/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node as build
2 |
3 | RUN mkdir /app
4 | WORKDIR /app
5 |
6 | COPY package*.json /app/
7 | RUN npm ci
8 |
9 | COPY . /app/
10 | RUN npm run build
11 |
12 | FROM nginx as final
13 |
14 | RUN apt-get update && apt-get upgrade -y \
15 | && apt-get clean
16 |
17 | RUN groupadd -g 1001 appuser && useradd -u 1001 -g appuser -s /bin/sh -m appuser
18 |
19 | RUN mkdir /app && chown -R appuser:appuser /app
20 |
21 | RUN mkdir -p /app /var/run/nginx /var/log/nginx /var/cache/nginx/ /sslcerts/ \
22 | && chown -R appuser:appuser /app /var/run/nginx /var/log/nginx /var/cache/nginx/ /sslcerts/
23 |
24 | COPY ssl/* /sslcerts/
25 | RUN chmod 644 /sslcerts/dev.crt && chmod 600 /sslcerts/dev.key && chown -R appuser:appuser /sslcerts/
26 |
27 | COPY nginx.conf /etc/nginx/nginx.conf
28 | COPY --from=build /app/dist/ /app
29 |
30 | RUN chown -R appuser:appuser /app
31 |
32 | USER 1001
33 |
34 | WORKDIR /app
35 |
--------------------------------------------------------------------------------
/product-api/Database/Database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const ProductModel = require('./ProductModel');
3 |
4 | const getMongoDBUrl = _ => {
5 | const mongodb_endpoint = process.env.MONGODB_ENDPOINT;
6 | const mongodb_port = process.env.MONGODB_PORT || 27017;
7 | const mongodb_username = process.env.MONGODB_USERNAME;
8 | const mongodb_password = process.env.MONGODB_PASSWORD;
9 | const mongodb_database = process.env.MONGODB_DATABASE;
10 | return `mongodb://${mongodb_username}:${mongodb_password}@${mongodb_endpoint}:${mongodb_port}/${mongodb_database}`;
11 | }
12 | const mongodb_url = getMongoDBUrl();
13 |
14 | class Database {
15 | constructor() {
16 | this._connect();
17 | }
18 |
19 | async _connect() {
20 | await mongoose.connect(mongodb_url);
21 | }
22 |
23 | async getProducts() {
24 | return await ProductModel.find();
25 | }
26 |
27 | async getProductById(id) {
28 | return await ProductModel.findById(id);
29 | }
30 | }
31 |
32 | module.exports = Database;
33 |
--------------------------------------------------------------------------------
/front-end/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "front-end",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "axios": "^1.7.2",
12 | "core-js": "^3.8.3",
13 | "vue": "^3.2.13"
14 | },
15 | "devDependencies": {
16 | "@babel/core": "^7.12.16",
17 | "@babel/eslint-parser": "^7.12.16",
18 | "@vue/cli-plugin-babel": "~5.0.0",
19 | "@vue/cli-plugin-eslint": "~5.0.0",
20 | "@vue/cli-service": "~5.0.0",
21 | "eslint": "^7.32.0",
22 | "eslint-plugin-vue": "^8.0.3"
23 | },
24 | "eslintConfig": {
25 | "root": true,
26 | "env": {
27 | "node": true
28 | },
29 | "extends": [
30 | "plugin:vue/vue3-essential",
31 | "eslint:recommended"
32 | ],
33 | "parserOptions": {
34 | "parser": "@babel/eslint-parser"
35 | },
36 | "rules": {}
37 | },
38 | "browserslist": [
39 | "> 1%",
40 | "last 2 versions",
41 | "not dead",
42 | "not ie 11"
43 | ]
44 | }
45 |
--------------------------------------------------------------------------------
/gitops/apps/front-end/front-end.yml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: front-end
5 | labels:
6 | app: front-end
7 | spec:
8 | # do not include replicas in the manifests if you want replicas to be controlled by HPA
9 | replicas: 1
10 | selector:
11 | matchLabels:
12 | app: front-end
13 | template:
14 | metadata:
15 | labels:
16 | app: front-end
17 | spec:
18 | securityContext:
19 | runAsNonRoot: true
20 | seccompProfile:
21 | type: RuntimeDefault
22 | containers:
23 | - name: front-end
24 | image: gustavoapolinario/ecommerce-lab-front-end
25 | ports:
26 | - containerPort: 80
27 | resources:
28 | requests:
29 | memory: "128Mi"
30 | cpu: "250m"
31 | limits:
32 | memory: "512Mi"
33 | cpu: "500m"
34 | ---
35 | apiVersion: v1
36 | kind: Service
37 | metadata:
38 | name: front-end-service
39 | spec:
40 | ports:
41 | - name: http
42 | port: 8080
43 | targetPort: 80
44 | selector:
45 | app: front-end
46 | type: NodePort
47 |
--------------------------------------------------------------------------------
/buy-api/Queue/Queue.js:
--------------------------------------------------------------------------------
1 | const amqp = require('amqplib');
2 | const RabbitMQ = require('./RabbitMQ')
3 |
4 | class Queue {
5 | constructor() {
6 | this.rabbitmq = new RabbitMQ();
7 | }
8 |
9 | async createAndBindIfNotExist(queue_name, exchange, routingKey) {
10 | await this.rabbitmq.assertExchange(exchange, 'direct', { durable: true });
11 | await this.rabbitmq.assertQueue(queue_name, { durable: true });
12 | await this.rabbitmq.bindQueue(queue_name, exchange, routingKey);
13 | }
14 |
15 | async sendMessage(queue_name, msg) {
16 | const exchange = `${queue_name}`
17 | const routingKey = `${queue_name}`
18 |
19 | await this.createAndBindIfNotExist(queue_name, exchange, routingKey);
20 | await this.rabbitmq.publish(exchange, routingKey, msg);
21 | }
22 |
23 | async createConsumer(queue_name, consumer_fn) {
24 | await this.rabbitmq.consume(queue_name, consumer_fn);
25 | }
26 |
27 | async ack(msg) {
28 | await this.rabbitmq.ack(msg);
29 | }
30 |
31 | async verifyConnection() {
32 | return await this.rabbitmq.verifyConnection();
33 | }
34 |
35 | async close() {
36 | await this.rabbitmq.close();
37 | }
38 | }
39 |
40 | module.exports = Queue;
--------------------------------------------------------------------------------
/buy-job/Queue/Queue.js:
--------------------------------------------------------------------------------
1 | const amqp = require('amqplib');
2 | const RabbitMQ = require('./RabbitMQ')
3 |
4 | class Queue {
5 | constructor() {
6 | this.rabbitmq = new RabbitMQ();
7 | }
8 |
9 | async createAndBindIfNotExist(queue_name, exchange, routingKey) {
10 | await this.rabbitmq.assertExchange(exchange, 'direct', { durable: true });
11 | await this.rabbitmq.assertQueue(queue_name, { durable: true });
12 | await this.rabbitmq.bindQueue(queue_name, exchange, routingKey);
13 | }
14 |
15 | async sendMessage(queue_name, msg) {
16 | const exchange = `${queue_name}`
17 | const routingKey = `${queue_name}`
18 |
19 | await this.createAndBindIfNotExist(queue_name, exchange, routingKey);
20 | await this.rabbitmq.publish(exchange, routingKey, msg);
21 | }
22 |
23 | async createConsumer(queue_name, consumer_fn) {
24 | await this.rabbitmq.consume(queue_name, consumer_fn);
25 | }
26 |
27 | async ack(msg) {
28 | await this.rabbitmq.ack(msg);
29 | }
30 |
31 | async verifyConnection() {
32 | return await this.rabbitmq.verifyConnection();
33 | }
34 |
35 | async close() {
36 | await this.rabbitmq.close();
37 | }
38 | }
39 |
40 | module.exports = Queue;
--------------------------------------------------------------------------------
/gitops/ArgoCD-apps.yml:
--------------------------------------------------------------------------------
1 | # argocd appset create gitops/ArgoCD-apps.yml --port-forward --port-forward-namespace argocd
2 | # argocd appset create gitops/ArgoCD-apps.yml --port-forward --port-forward-namespace argocd --upsert # Update
3 | # argocd appset delete eccomerce-lab-apps --port-forward --port-forward-namespace argocd
4 | apiVersion: argoproj.io/v1alpha1
5 | kind: ApplicationSet
6 | metadata:
7 | name: eccomerce-lab-apps
8 | # namespace: argocd
9 | spec:
10 | generators:
11 | - git:
12 | repoURL: https://github.com/gustavoapolinario/eccomerce-lab.git
13 | revision: HEAD
14 | directories:
15 | - path: gitops/apps/*
16 | template:
17 | metadata:
18 | name: '{{path.basename}}'
19 | spec:
20 | project: "default"
21 | source:
22 | repoURL: https://github.com/gustavoapolinario/eccomerce-lab.git
23 | targetRevision: HEAD
24 | path: '{{path}}'
25 | destination:
26 | server: https://kubernetes.default.svc
27 | namespace: 'default'
28 | syncPolicy:
29 | syncOptions:
30 | - CreateNamespace=true
31 | automated:
32 | selfHeal: true
33 | prune: true
34 | allowEmpty: true
35 |
--------------------------------------------------------------------------------
/buy-job/consumer.js:
--------------------------------------------------------------------------------
1 | const Queue = require('./Queue/Queue');
2 | const Event = require('./Event/Event');
3 |
4 | const queue_name = 'buy_requests';
5 |
6 | class Consumer {
7 | constructor() {
8 | this.queue = new Queue();
9 | this.event = new Event();
10 | }
11 |
12 | async createConsumer() {
13 | const consumer = async (msg) => {
14 | if (msg !== null) {
15 | const msg_str = msg.content.toString()
16 | console.log(`[x] Received: ${msg_str}`);
17 |
18 | await this.event.connect();
19 |
20 | const result = await this.event.sendMessage('test-topic', [msg_str]);
21 |
22 | // await this.event.disconnect();
23 |
24 | // Acknowledge the message
25 | this.queue.ack(msg);
26 | }
27 | };
28 |
29 | console.log(`[*] Waiting for messages in queue ${queue_name}`);
30 | await this.event.createTopic('test-topic')
31 | await this.queue.createConsumer(queue_name, consumer)
32 | }
33 |
34 | async healthcheck() {
35 | let result = await this.queue.verifyConnection()
36 | if( !result ) {
37 | await this.createConsumer()
38 | result = await this.queue.verifyConnection()
39 | }
40 | return result
41 | }
42 |
43 | }
44 |
45 | module.exports = Consumer
46 |
--------------------------------------------------------------------------------
/buy-api/Routes/buyRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const RouteErrorHandler = require('../RouteErrorHandler');
3 | const Queue = require('../Queue/Queue');
4 |
5 | const router = express.Router();
6 |
7 | const axios = require('axios');
8 | const product_api_url = process.env.PRODUCT_API_URL;
9 |
10 |
11 | const getProduct = async (productId) => {
12 | const productResponse = await axios.get(`${product_api_url}/products/${productId}`);
13 | return productResponse.data;
14 | };
15 |
16 | const publishBuyRequest = async (product) => {
17 | const queue = new Queue();
18 |
19 | const queue_name = 'buy_requests';
20 | const msg = JSON.stringify({ productId: product._id, productName: product.name });
21 | queue.sendMessage(queue_name, msg);
22 | queue.close();
23 | };
24 |
25 | // Buy a product by ID
26 | router.post('/buy/:id', RouteErrorHandler(async (req, res) => {
27 | const productId = req.params.id;
28 | const product = await getProduct(productId);
29 | if (!product) {
30 | return res.status(404).json({ message: 'Product not found' });
31 | }
32 |
33 | await publishBuyRequest(product);
34 | res.json({ message: `Your purchase of ${product.name} has been accepted, we will process it shortly.` });
35 | }));
36 |
37 | module.exports = router;
38 |
--------------------------------------------------------------------------------
/gitops/databases/redis.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: redis
5 | spec:
6 | ports:
7 | - port: 6379
8 | clusterIP: None
9 | selector:
10 | app: redis
11 | ---
12 | apiVersion: apps/v1
13 | kind: StatefulSet
14 | metadata:
15 | name: redis
16 | spec:
17 | selector:
18 | matchLabels:
19 | app: redis
20 | serviceName: redis
21 | replicas: 1
22 | template:
23 | metadata:
24 | labels:
25 | app: redis
26 | spec:
27 | securityContext:
28 | runAsNonRoot: false
29 | seccompProfile:
30 | type: RuntimeDefault
31 | containers:
32 | - name: redis
33 | image: redis:latest
34 | args: ["redis-server", "--requirepass", "your_redis_password"]
35 | ports:
36 | - name: app
37 | containerPort: 6379
38 | volumeMounts:
39 | - name: redis-persistent-storage
40 | mountPath: /data
41 | persistentVolumeClaimRetentionPolicy:
42 | whenDeleted: Retain
43 | whenScaled: Delete
44 | volumeClaimTemplates:
45 | - metadata:
46 | name: redis-persistent-storage
47 | spec:
48 | accessModes: ["ReadWriteOnce"]
49 | resources:
50 | requests:
51 | storage: 300Mi
52 |
--------------------------------------------------------------------------------
/product-api/Routes/healthCheckRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const Cache = require('../Cache/Cache');
3 | const Database = require('../Database/Database');
4 | const router = express.Router();
5 |
6 | /**
7 | * Health check to be used on Liveness Probes health check
8 | */
9 | router.get('/healthcheck', async (req, res) => {
10 | try {
11 | res.status(200).json({ status: 'healthy' });
12 | } catch (err) {
13 | console.error('Health check failed:', err);
14 | res.status(500).json({ status: 'unhealthy', error: err.message });
15 | }
16 | });
17 |
18 | /**
19 | * Health check to test integrations connection, don't use as Liveness Probes health check
20 | * The health check need to test the health of the service, not the databases.
21 | * But this route can help in troubleshooting
22 | */
23 | router.get('/healthcheck-integrations', async (req, res) => {
24 | try {
25 | const cache = new Cache();
26 | const db = new Database();
27 |
28 | // Check Redis connection
29 | await cache.client.ping();
30 |
31 | // Check MongoDB connection
32 | await db._connect();
33 |
34 | res.status(200).json({ status: 'healthy' });
35 | } catch (err) {
36 | console.error('Health check failed:', err);
37 | res.status(500).json({ status: 'unhealthy', error: err.message });
38 | }
39 | });
40 |
41 | module.exports = router;
42 |
--------------------------------------------------------------------------------
/buy-job/Routes/healthCheckRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const healthCheckRoute = consumer => {
5 | /**
6 | * Health check to be used on Liveness Probes health check
7 | */
8 | router.get('/healthcheck', async (req, res) => {
9 | try {
10 | res.status(200).json({ status: 'healthy' });
11 | } catch (err) {
12 | console.error('Health check failed:', err);
13 | res.status(500).json({ status: 'unhealthy', error: err.message });
14 | }
15 | });
16 |
17 | /**
18 | * Health check to test integrations, don't use as Liveness Probes health check
19 | * The health check need to test the health of the service, not the databases.
20 | * But this route can help in troubleshooting
21 | */
22 | router.get('/healthcheck-integrations', async (req, res) => {
23 | try {
24 | console.log("/healthcheck-integrations")
25 | const health = await consumer.healthcheck();
26 |
27 | console.log("/healthcheck-integrations ok")
28 | if ( health )
29 | res.status(200).json({ status: 'healthy' });
30 | else {
31 | res.status(500).json({ status: 'unhealthy' });
32 | }
33 | } catch (err) {
34 | console.error('Health check failed:', err);
35 | res.status(500).json({ status: 'unhealthy', error: err.message });
36 | }
37 | });
38 |
39 | return router
40 | }
41 |
42 | module.exports = healthCheckRoute;
43 |
--------------------------------------------------------------------------------
/front-end/src/components/DialogBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
27 |
28 |
63 |
--------------------------------------------------------------------------------
/gitops/databases/rabbitmq.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: rabbitmq
5 | spec:
6 | ports:
7 | - name: service
8 | port: 5672
9 | - name: adm
10 | port: 15672
11 | clusterIP: None
12 | selector:
13 | app: rabbitmq
14 | ---
15 | apiVersion: apps/v1
16 | kind: StatefulSet
17 | metadata:
18 | name: rabbitmq
19 | spec:
20 | selector:
21 | matchLabels:
22 | app: rabbitmq
23 | serviceName: rabbitmq
24 | replicas: 1
25 | template:
26 | metadata:
27 | labels:
28 | app: rabbitmq
29 | spec:
30 | securityContext:
31 | runAsNonRoot: false
32 | seccompProfile:
33 | type: RuntimeDefault
34 | containers:
35 | - name: rabbitmq
36 | image: rabbitmq:3-management
37 | ports:
38 | - name: app
39 | containerPort: 5672
40 | - name: adm
41 | containerPort: 15672
42 | env:
43 | - name: RABBITMQ_DEFAULT_USER
44 | value: rabbitmq
45 | - name: RABBITMQ_DEFAULT_PASS
46 | value: rabbitmq-password
47 | volumeMounts:
48 | - name: rabbitmq-persistent-storage
49 | mountPath: /var/lib/rabbitmq
50 | persistentVolumeClaimRetentionPolicy:
51 | whenDeleted: Retain
52 | whenScaled: Delete
53 | volumeClaimTemplates:
54 | - metadata:
55 | name: rabbitmq-persistent-storage
56 | spec:
57 | accessModes: ["ReadWriteOnce"]
58 | resources:
59 | requests:
60 | storage: 1Gi
61 |
--------------------------------------------------------------------------------
/gitops/databases-helm/mongodb/mongodb-configmap.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: ConfigMap
3 | metadata:
4 | name: mongo-init-configmap
5 | data:
6 | initdb.js: |
7 | db = db.getSiblingDB('products'); // Connect to your database (e.g., 'products')
8 |
9 | // Optional: Initialize collections and documents
10 | db.createCollection('products');
11 |
12 | const products = [
13 | { name: 'DVD The Haunting Shadows', price: 80, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Haunting+Shadows' },
14 | { name: 'DVD Whispers in the Dark', price: 20, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Whispers+in+the+Dark' },
15 | { name: 'DVD The Dollmaker\'s Secret', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Dollmaker\'s+Secret' },
16 | { name: 'DVD Echoes of the Past', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Echoes+of+the+Past' },
17 | { name: 'DVD The Cursed Mirror', price: 50, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Cursed+Mirror' },
18 | { name: 'DVD Nightmare Symphony', price: 10, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Nightmare+Symphony' },
19 | { name: 'DVD The Forgotten Cemetery', price: 60, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Forgotten+Cemetery' },
20 | { name: 'DVD The Haunted Carnival', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Haunted+Carnival' },
21 | ];
22 |
23 | db.products.insertMany(products);
24 |
--------------------------------------------------------------------------------
/mongodb/mongo-init.js:
--------------------------------------------------------------------------------
1 | // mongo-init.js
2 | db = db.getSiblingDB('products'); // Connect to your database (e.g., 'test')
3 | db.createUser({
4 | user: 'product-api',
5 | pwd: 'product-api-password',
6 | roles: [
7 | {
8 | role: 'readWrite',
9 | db: 'products'
10 | }
11 | ]
12 | });
13 |
14 | // Optional: Initialize collections and documents
15 | db.createCollection('products');
16 |
17 | const products = [
18 | { name: 'DVD The Haunting Shadows', price: 80, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Haunting+Shadows' },
19 | { name: 'DVD Whispers in the Dark', price: 20, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Whispers+in+the+Dark' },
20 | { name: 'DVD The Dollmaker\'s Secret', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Dollmaker\'s+Secret' },
21 | { name: 'DVD Echoes of the Past', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Echoes+of+the+Past' },
22 | { name: 'DVD The Cursed Mirror', price: 50, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Cursed+Mirror' },
23 | { name: 'DVD Nightmare Symphony', price: 10, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Nightmare+Symphony' },
24 | { name: 'DVD The Forgotten Cemetery', price: 60, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Forgotten+Cemetery' },
25 | { name: 'DVD The Haunted Carnival', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Haunted+Carnival' },
26 | ];
27 |
28 | db.products.insertMany(products);
29 |
--------------------------------------------------------------------------------
/buy-job/Event/Kafka.js:
--------------------------------------------------------------------------------
1 | const { Kafka: kafkajs } = require('kafkajs')
2 |
3 | const getKafkaConfig = _ => {
4 | const kafka_endpoint = process.env.KAFKA_ENDPOINT
5 | const kafka_port = process.env.KAFKA_PORT || 9092
6 | return {
7 | clientId: "my-producer",
8 | brokers: [`${kafka_endpoint}:${kafka_port}`],
9 | }
10 | }
11 |
12 | const kafka_config = getKafkaConfig()
13 |
14 | class Kafka {
15 | constructor() {
16 | console.log(kafka_config)
17 | this.kafka = new kafkajs(kafka_config);
18 | this.admin = this.kafka.admin();
19 | this.producer = this.kafka.producer();
20 | }
21 |
22 | async createTopic(topic) {
23 | try {
24 | await this.admin.connect();
25 | await this.admin.createTopics({
26 | topics: [{
27 | topic: topic,
28 | numPartitions: 3,
29 | replicationFactor: 1
30 | }]
31 | });
32 | console.log('Topic created successfully');
33 | } catch (error) {
34 | console.error('Error creating topic:', error);
35 | } finally {
36 | await this.admin.disconnect();
37 | }
38 | }
39 |
40 | async connect() {
41 | await this.producer.connect();
42 | console.log('Producer connected');
43 | }
44 |
45 | async sendMessage(topic, messages) {
46 | await this.producer.send({
47 | topic: topic,
48 | messages: messages.map(message => ({ value: message })),
49 | });
50 | console.log('Message sent:', messages);
51 | }
52 |
53 | async disconnect() {
54 | await this.producer.disconnect();
55 | console.log('Producer disconnected');
56 | }
57 | }
58 |
59 | module.exports = Kafka;
60 |
--------------------------------------------------------------------------------
/gitops/apps/buy-job/buy-job.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: buy-job-service
5 | spec:
6 | ports:
7 | - name: http
8 | port: 3002
9 | targetPort: 3002
10 | selector:
11 | app: buy-job
12 | type: NodePort
13 | ---
14 | apiVersion: apps/v1
15 | kind: Deployment
16 | metadata:
17 | name: buy-job
18 | labels:
19 | app: buy-job
20 | spec:
21 | # do not include replicas in the manifests if you want replicas to be controlled by HPA
22 | replicas: 1
23 | selector:
24 | matchLabels:
25 | app: buy-job
26 | template:
27 | metadata:
28 | labels:
29 | app: buy-job
30 | spec:
31 | securityContext:
32 | runAsNonRoot: true
33 | seccompProfile:
34 | type: RuntimeDefault
35 | containers:
36 | - name: buy-job
37 | image: gustavoapolinario/ecommerce-lab-buy-job
38 | ports:
39 | - containerPort: 3002
40 | resources:
41 | requests:
42 | memory: "128Mi"
43 | cpu: "250m"
44 | limits:
45 | memory: "512Mi"
46 | cpu: "500m"
47 | env:
48 | - name: "PORT"
49 | value: "3002"
50 | - name: "RABBITMQ_ENDPOINT"
51 | value: "eccomerce-lab-rabbitmq"
52 | - name: "RABBITMQ_PORT"
53 | value: "5672"
54 | - name: "RABBITMQ_USERNAME"
55 | value: "rabbitmq"
56 | - name: "RABBITMQ_PASSWORD"
57 | value: "rabbitmq-password"
58 | - name: "KAFKA_ENDPOINT"
59 | value: "eccomerce-lab-kafka"
60 | - name: "KAFKA_PORT"
61 | value: "9092"
62 |
--------------------------------------------------------------------------------
/product-api/Cache/Cache.js:
--------------------------------------------------------------------------------
1 | const redis = require('redis');
2 |
3 | const getRedisUrl = _ => {
4 | const redis_endpoint = process.env.REDIS_ENDPOINT;
5 | const redis_port = process.env.REDIS_PORT;
6 | const redis_username = process.env.REDIS_USERNAME;
7 | const redis_password = process.env.REDIS_PASSWORD;
8 | return `redis://${redis_username}:${redis_password}@${redis_endpoint}:${redis_port}`;
9 | }
10 | const redis_url = getRedisUrl();
11 |
12 | class Cache {
13 | constructor() {
14 | this.client = redis.createClient({
15 | url: redis_url
16 | });
17 | this.client.connect()
18 | process.on("exit", () => { this.client.quit(); });
19 | }
20 |
21 | async cacheProductListAsync(products) {
22 | await this.client.setEx('products', 3600, JSON.stringify(products)); // Cache the products for 1 hour
23 | }
24 |
25 | async deleteProductListAsync() {
26 | await this.client.del('products');
27 | }
28 |
29 | async getProductListAsync() {
30 | const data = await this.client.get('products');
31 | return data ? JSON.parse(data) : null;
32 | }
33 |
34 | async cacheProductAsync(productId, product) {
35 | await this.client.setEx(`product-${productId}`, 3600, JSON.stringify(product)); // Cache the products for 1 hour
36 | }
37 |
38 | async deleteProductAsync(productId) {
39 | await this.client.del(`product-${productId}`);
40 | }
41 |
42 | async getProductAsync(productId) {
43 | const data = await this.client.get(`product-${productId}`);
44 | return data ? JSON.parse(data) : null;
45 | }
46 |
47 | async close() {
48 | this.client.quit();
49 | }
50 | }
51 |
52 | module.exports = Cache;
53 |
--------------------------------------------------------------------------------
/product-api/Routes/productRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const Cache = require('../Cache/Cache');
3 | const Database = require('../Database/Database');
4 | const RouteErrorHandler = require('../RouteErrorHandler');
5 |
6 | const router = express.Router();
7 | const cache = new Cache();
8 | const db = new Database();
9 |
10 | // Middleware to check cache
11 | const checkCacheProductList = async (req, res, next) => {
12 | try {
13 | const data = await cache.getProductListAsync();
14 | if (data) {
15 | return res.status(200).json(data);
16 | } else {
17 | next();
18 | }
19 | } catch (err) {
20 | console.error('Cache error: ', err);
21 | next();
22 | }
23 | };
24 | const checkCacheProductId = async (req, res, next) => {
25 | try {
26 | const data = await cache.getProductAsync();
27 | if (data) {
28 | return res.status(200).json(data);
29 | } else {
30 | next();
31 | }
32 | } catch (err) {
33 | console.error('Cache error: ', err);
34 | next();
35 | }
36 | };
37 |
38 | // Get all products with caching
39 | router.get('/products', checkCacheProductList, RouteErrorHandler(async (req, res) => {
40 | const productId = req.params.id;
41 | const products = await db.getProducts(productId);
42 | await cache.cacheProductListAsync(products);
43 | res.status(200).json(products);
44 | }));
45 |
46 | router.get('/products/:id', checkCacheProductId, RouteErrorHandler(async (req, res) => {
47 | const productId = req.params.id
48 | const product = await db.getProductById(productId);
49 | if (product) {
50 | await cache.cacheProductAsync(productId, product);
51 | res.json(product);
52 | } else {
53 | res.status(404).json({ message: 'Product not found' });
54 | }
55 | }));
56 |
57 | module.exports = router;
58 |
--------------------------------------------------------------------------------
/gitops/apps/product-api/product-api.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: product-api-service
5 | spec:
6 | ports:
7 | - name: http
8 | port: 3000
9 | targetPort: 3000
10 | selector:
11 | app: product-api
12 | type: NodePort
13 | ---
14 | apiVersion: apps/v1
15 | kind: Deployment
16 | metadata:
17 | name: product-api
18 | labels:
19 | app: product-api
20 | spec:
21 | # do not include replicas in the manifests if you want replicas to be controlled by HPA
22 | replicas: 1
23 | selector:
24 | matchLabels:
25 | app: product-api
26 | template:
27 | metadata:
28 | labels:
29 | app: product-api
30 | spec:
31 | securityContext:
32 | runAsNonRoot: true
33 | seccompProfile:
34 | type: RuntimeDefault
35 | containers:
36 | - name: product-api
37 | image: gustavoapolinario/ecommerce-lab-product-api
38 | ports:
39 | - containerPort: 3000
40 | resources:
41 | requests:
42 | memory: "128Mi"
43 | cpu: "250m"
44 | limits:
45 | memory: "512Mi"
46 | cpu: "500m"
47 | env:
48 | - name: PORT
49 | value: "3000"
50 | - name: MONGODB_ENDPOINT
51 | value: "eccomerce-lab-mongodb"
52 | - name: MONGODB_PORT
53 | value: "27017"
54 | - name: MONGODB_USERNAME
55 | value: "product-api"
56 | - name: MONGODB_PASSWORD
57 | value: "product-api-password"
58 | - name: MONGODB_DATABASE
59 | value: "products"
60 | - name: REDIS_ENDPOINT
61 | value: "eccomerce-lab-redis-master"
62 | - name: REDIS_PORT
63 | value: "6379"
64 | - name: REDIS_USERNAME
65 | value: "default"
66 | - name: REDIS_PASSWORD
67 | value: "your_redis_password"
68 |
--------------------------------------------------------------------------------
/gitops/apps/buy-api/buy-api.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: buy-api-service
5 | spec:
6 | ports:
7 | - name: http
8 | port: 3001
9 | targetPort: 3001
10 | selector:
11 | app: buy-api
12 | type: NodePort
13 | ---
14 | apiVersion: apps/v1
15 | kind: Deployment
16 | metadata:
17 | name: buy-api
18 | labels:
19 | app: buy-api
20 | spec:
21 | # do not include replicas in the manifests if you want replicas to be controlled by HPA
22 | replicas: 1
23 | selector:
24 | matchLabels:
25 | app: buy-api
26 | template:
27 | metadata:
28 | labels:
29 | app: buy-api
30 | spec:
31 | securityContext:
32 | runAsNonRoot: true
33 | seccompProfile:
34 | type: RuntimeDefault
35 | containers:
36 | - name: buy-api
37 | image: gustavoapolinario/ecommerce-lab-buy-api
38 | ports:
39 | - containerPort: 3001
40 | resources:
41 | requests:
42 | memory: "128Mi"
43 | cpu: "250m"
44 | limits:
45 | memory: "512Mi"
46 | cpu: "500m"
47 | env:
48 | - name: "PORT"
49 | value: "3001"
50 | - name: "PRODUCT_API_URL"
51 | value: "http://product-api-service:3000" # use service to connect on pod internally
52 | - name: "RABBITMQ_ENDPOINT"
53 | value: "eccomerce-lab-rabbitmq"
54 | - name: "RABBITMQ_PORT"
55 | value: "5672"
56 | - name: "RABBITMQ_USERNAME"
57 | value: "rabbitmq"
58 | - name: "RABBITMQ_PASSWORD"
59 | value: "rabbitmq-password"
60 | - name: "REDIS_ENDPOINT"
61 | value: "eccomerce-lab-redis-master"
62 | - name: "REDIS_PORT"
63 | value: "6379"
64 | - name: "REDIS_USERNAME"
65 | value: "default"
66 | - name: "REDIS_PASSWORD"
67 | value: "your_redis_password"
68 |
--------------------------------------------------------------------------------
/front-end/ssl/dev.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIGOTCCBCGgAwIBAgIUQJQvnIa5qfgsk9NraV089POtMHQwDQYJKoZIhvcNAQEL
3 | BQAwgasxCzAJBgNVBAYTAkJSMRIwEAYDVQQIDAlTYW8gUGF1bG8xEjAQBgNVBAcM
4 | CVNhbyBQYXVsbzEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ0w
5 | CwYDVQQLDARsYWJzMRswGQYDVQQDDBJHdXN0YXZvIEFwb2xpbmFyaW8xJTAjBgkq
6 | hkiG9w0BCQEWFmd1c3Rhdm8uZ3Vzc0BnbWFpbC5jb20wHhcNMjUwOTEyMTQzNTQ1
7 | WhcNMzUwOTEwMTQzNTQ1WjCBqzELMAkGA1UEBhMCQlIxEjAQBgNVBAgMCVNhbyBQ
8 | YXVsbzESMBAGA1UEBwwJU2FvIFBhdWxvMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRn
9 | aXRzIFB0eSBMdGQxDTALBgNVBAsMBGxhYnMxGzAZBgNVBAMMEkd1c3Rhdm8gQXBv
10 | bGluYXJpbzElMCMGCSqGSIb3DQEJARYWZ3VzdGF2by5ndXNzQGdtYWlsLmNvbTCC
11 | AiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOBvI2dv6Jsx3B2VJXQRlilF
12 | igmcwcPH0VrZVHkiv67ANborFIo/ahnld5ffbrXNhrHIoloLNuv7wr8ODpRGSV2D
13 | sgDugCkq3wvMIKfYK3S1rRei8MYkJlsYGzB2EAKhZ0ysuKprdwsT9ddkRPOg+ClN
14 | yaieIp54ly7K2KB6GgCGrYGI1VvFyUGsufcMcLMsVsnu2bXHT4A/NQwzbYz5EXPo
15 | A0LBlo5MNEBwRFU78dZZqdqwVkH8fBI/sNcv3rRz6SpYlUGP/YGpvaEjkJZpI8CB
16 | wQWIL52D5ECmRPfpK0aBlkYJXoNFL+FCO/9A7W1bdRE4bRRF9Y18K6I87e/TBrKz
17 | 4xa75JHpcwnG+rCBGwEZtiifqM4KVNEwmLca9SGkGg5wFimPQonbpPSg/aIMhu9U
18 | DXd4tKAduU4sTDAeUlAP5KWHc2BdMNFvAJOXdNysWSQk9zMdFv89EB/PHJRvLAf5
19 | jBQySYdkqBa297/lm9QHF0TLYmpOO2snrd7VqWU9ejp7jLH/Ot2VSI71ap97niEH
20 | Oq60SrGuFd+ljS835KcLqYvi6OU+LoXOQQnY/mXWMOuY2N5MVLWgpc4VUWL2sDU7
21 | PJnjrvooIIwyxzirDUdYPHalm26PB65XZYyFC4TqF021vsaZU430vOde2nXXsIHd
22 | TFh4dZKqNk5yV1VKXvJlAgMBAAGjUzBRMB0GA1UdDgQWBBSjaWrHaZ0JapFJV5bZ
23 | LmEN3nr4EzAfBgNVHSMEGDAWgBSjaWrHaZ0JapFJV5bZLmEN3nr4EzAPBgNVHRMB
24 | Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQBtcI/PqAwWNl2F6z1sURgmdI3z
25 | 6eDuPzYN50jeVBb0kAzp82mw0oaS44bP/8Vakc6vubEWubuDLn0L72ugeGCALx9t
26 | XrvIdu2ku1d1/lHGVgxy+lZ6RKVRtXFv/2kLevaKTjfChdCpRIjmYnkzybH+1pE0
27 | LO6AgOvBkfHTMwHkaaRC4usXHt5KptiRhH9Z1zM3jjJSh4kLDsvYAJv0ihG61K3C
28 | w/xLhQS3fsvbYWgEgyiacZobxVk32qzUvWuIpkmT4BdLRVEjcdjRwEQ3RxoA0VLs
29 | yNOcfIQsdEn6rWpy1fIJ/Fw+zF8izv0VtCL9JY+5ZtoJ+LU0dBBP8H1e2EcfLE6S
30 | sKKPVzaEgmGiCV53hmgC5fKl9CBDMl5fKoOGYf9/lEtyjPnhlPSO05lYqYt+Bzxe
31 | 55/zOUBnhir4kfMjKMVq2UOPFIeYe/a0w5yNYXiR6cKNU5BV6bs5VJ48oKcGR1DG
32 | KzWcFG4MQgOB2ShERc3sXYtSLQfAxVln0d4sL1vI3kVZPGTc6lJIBVeV19CyjkyJ
33 | CIr3aBsxbrUC62zdW64d/+t5llvq+/HP3TqC6iJ3UeCmgbkSl8i3Yjf4b6wJiAiX
34 | ba9bceEhdQ+YHo5rPKU9eDNJzqZcoKeicS+MWM/QPuizEBODKjqc7M8TRNLIyIxQ
35 | szzgQr5D1xXYs2EvFg==
36 | -----END CERTIFICATE-----
37 |
--------------------------------------------------------------------------------
/front-end/src/components/ProductList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Vintage Video rental
4 |
5 | -
6 |
7 |
8 |
{{ product.name }}
9 |
${{ product.price }}
10 |
11 |
12 |
13 |
14 |
15 | {{ dialogMessage }}
16 |
17 |
18 |
19 |
20 |
63 |
64 |
102 |
--------------------------------------------------------------------------------
/buy-api/Queue/RabbitMQ.js:
--------------------------------------------------------------------------------
1 | const amqp = require('amqplib');
2 |
3 | const getRabbitMQUrl = _ => {
4 | const rabbitmq_endpoint = process.env.RABBITMQ_ENDPOINT;
5 | const rabbitmq_port = process.env.RABBITMQ_PORT || 5672;
6 | const rabbitmq_username = process.env.RABBITMQ_USERNAME;
7 | const rabbitmq_password = process.env.RABBITMQ_PASSWORD;
8 | return `amqp://${rabbitmq_username}:${rabbitmq_password}@${rabbitmq_endpoint}:${rabbitmq_port}`;
9 | }
10 | const rabbitMQ_url = getRabbitMQUrl();
11 |
12 | class RabbitMQ {
13 | constructor() {
14 | this.connection = null;
15 | this.channel = null;
16 | this.connected = false;
17 |
18 | process.on("exit", () => { this.close() });
19 | }
20 |
21 | async connect() {
22 | if( !this.connection || !this.connected ) {
23 | this.connected = true;
24 | this.connection = await amqp.connect(rabbitMQ_url)
25 | await this.createListeners()
26 | }
27 | }
28 |
29 | async createListeners() {
30 |
31 | this.connection.on('error', (err) => {
32 | console.log(err)
33 | this.connected = false;
34 | })
35 |
36 | this.connection.on('close', _ => this.connected = false)
37 | this.connection.on('blocked', _ => this.connected = false)
38 | this.connection.on('unblocked', _ => this.connected = true)
39 | }
40 |
41 | async connectChannel() {
42 | if (!this.channel || !this.connected) {
43 | await this.connect()
44 | this.channel = await this.connection.createChannel();
45 | }
46 | }
47 |
48 | async assertExchange(exchange, type, options) {
49 | await this.connectChannel()
50 | await this.channel.assertExchange(exchange, type, options);
51 | }
52 |
53 | async assertQueue(queue_name, options) {
54 | await this.connectChannel()
55 | await this.channel.assertQueue(queue_name, options);
56 | }
57 |
58 | async bindQueue(queue_name, exchange, routingKey) {
59 | await this.connectChannel()
60 | await this.channel.bindQueue(queue_name, exchange, routingKey);
61 | }
62 |
63 | async publish(exchange, routingKey, message) {
64 | await this.connectChannel()
65 | await this.channel.publish(exchange, routingKey, Buffer.from(message));
66 | console.log('Sent message to RabbitMQ:', message);
67 | }
68 |
69 | async consume(queue_name, consumer_fn) {
70 | await this.connectChannel()
71 | await this.channel.consume(queue_name, consumer_fn, {
72 | noAck: false
73 | });
74 | }
75 |
76 | async verifyConnection() {
77 | await this.connect()
78 | return this.connected
79 | }
80 |
81 | async ack(msg) {
82 | await this.channel.ack(msg);
83 | }
84 |
85 | async closeConnection() {
86 | if (this.connection) {
87 | await this.connection.close();
88 | }
89 | }
90 |
91 | async closeChannel() {
92 | if (this.channel) {
93 | await this.channel.close();
94 | }
95 | }
96 |
97 | async close() {
98 | await this.closeChannel()
99 | await this.closeConnection()
100 | }
101 | }
102 |
103 | module.exports = RabbitMQ;
104 |
--------------------------------------------------------------------------------
/buy-job/Queue/RabbitMQ.js:
--------------------------------------------------------------------------------
1 | const amqp = require('amqplib');
2 |
3 | const getRabbitMQUrl = _ => {
4 | const rabbitmq_endpoint = process.env.RABBITMQ_ENDPOINT;
5 | const rabbitmq_port = process.env.RABBITMQ_PORT || 5672;
6 | const rabbitmq_username = process.env.RABBITMQ_USERNAME;
7 | const rabbitmq_password = process.env.RABBITMQ_PASSWORD;
8 | return `amqp://${rabbitmq_username}:${rabbitmq_password}@${rabbitmq_endpoint}:${rabbitmq_port}`;
9 | }
10 | const rabbitMQ_url = getRabbitMQUrl();
11 |
12 | class RabbitMQ {
13 | constructor() {
14 | this.connection = null;
15 | this.channel = null;
16 | this.connected = false;
17 |
18 | process.on("exit", () => { this.close() });
19 | }
20 |
21 | async connect() {
22 | if( !this.connection || !this.connected ) {
23 | this.connected = true;
24 | this.connection = await amqp.connect(rabbitMQ_url)
25 | await this.createListeners()
26 | }
27 | }
28 |
29 | async createListeners() {
30 |
31 | this.connection.on('error', (err) => {
32 | console.log(err)
33 | this.connected = false;
34 | })
35 |
36 | this.connection.on('close', _ => this.connected = false)
37 | this.connection.on('blocked', _ => this.connected = false)
38 | this.connection.on('unblocked', _ => this.connected = true)
39 | }
40 |
41 | async connectChannel() {
42 | if (!this.channel || !this.connected) {
43 | await this.connect()
44 | this.channel = await this.connection.createChannel();
45 | }
46 | }
47 |
48 | async assertExchange(exchange, type, options) {
49 | await this.connectChannel()
50 | await this.channel.assertExchange(exchange, type, options);
51 | }
52 |
53 | async assertQueue(queue_name, options) {
54 | await this.connectChannel()
55 | await this.channel.assertQueue(queue_name, options);
56 | }
57 |
58 | async bindQueue(queue_name, exchange, routingKey) {
59 | await this.connectChannel()
60 | await this.channel.bindQueue(queue_name, exchange, routingKey);
61 | }
62 |
63 | async publish(exchange, routingKey, message) {
64 | await this.connectChannel()
65 | await this.channel.publish(exchange, routingKey, Buffer.from(message));
66 | console.log('Sent message to RabbitMQ:', message);
67 | }
68 |
69 | async consume(queue_name, consumer_fn) {
70 | await this.connectChannel()
71 | await this.channel.consume(queue_name, consumer_fn, {
72 | noAck: false
73 | });
74 | }
75 |
76 | async verifyConnection() {
77 | await this.connect()
78 | return this.connected
79 | }
80 |
81 | async ack(msg) {
82 | await this.channel.ack(msg);
83 | }
84 |
85 | async closeConnection() {
86 | if (this.connection) {
87 | await this.connection.close();
88 | }
89 | }
90 |
91 | async closeChannel() {
92 | if (this.channel) {
93 | await this.channel.close();
94 | }
95 | }
96 |
97 | async close() {
98 | await this.closeChannel()
99 | await this.closeConnection()
100 | }
101 | }
102 |
103 | module.exports = RabbitMQ;
104 |
--------------------------------------------------------------------------------
/gitops/databases/mongodb.yml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: mongo-mongodb
5 | spec:
6 | ports:
7 | - port: 27017
8 | clusterIP: None
9 | selector:
10 | app: mongo
11 | ---
12 | apiVersion: apps/v1
13 | kind: StatefulSet
14 | metadata:
15 | name: mongo
16 | spec:
17 | selector:
18 | matchLabels:
19 | app: mongo
20 | serviceName: mongo
21 | replicas: 1
22 | template:
23 | metadata:
24 | labels:
25 | app: mongo
26 | spec:
27 | securityContext:
28 | runAsNonRoot: false
29 | seccompProfile:
30 | type: RuntimeDefault
31 | containers:
32 | - name: mongo
33 | image: mongo
34 | ports:
35 | - containerPort: 27017
36 | volumeMounts:
37 | - name: mongo-persistent-storage
38 | mountPath: /data/db
39 | - name: init-script
40 | mountPath: /docker-entrypoint-initdb.d/mongo-init.js
41 | subPath: mongo-init.js
42 | volumes:
43 | - name: init-script
44 | configMap:
45 | name: mongo-init-configmap
46 | persistentVolumeClaimRetentionPolicy:
47 | whenDeleted: Retain
48 | whenScaled: Delete
49 | volumeClaimTemplates:
50 | - metadata:
51 | name: mongo-persistent-storage
52 | spec:
53 | accessModes: ["ReadWriteOnce"]
54 | resources:
55 | requests:
56 | storage: 1Gi
57 | ---
58 | apiVersion: v1
59 | kind: ConfigMap
60 | metadata:
61 | name: mongo-init-configmap
62 | data:
63 | mongo-init.js: |
64 | db = db.getSiblingDB('products'); // Connect to your database (e.g., 'test')
65 | db.createUser({
66 | user: 'product-api',
67 | pwd: 'product-api-password',
68 | roles: [
69 | {
70 | role: 'readWrite',
71 | db: 'products'
72 | }
73 | ]
74 | });
75 |
76 | // Optional: Initialize collections and documents
77 | db.createCollection('products');
78 |
79 | const products = [
80 | { name: 'DVD The Haunting Shadows', price: 80, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Haunting+Shadows' },
81 | { name: 'DVD Whispers in the Dark', price: 20, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Whispers+in+the+Dark' },
82 | { name: 'DVD The Dollmaker\'s Secret', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Dollmaker\'s+Secret' },
83 | { name: 'DVD Echoes of the Past', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Echoes+of+the+Past' },
84 | { name: 'DVD The Cursed Mirror', price: 50, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Cursed+Mirror' },
85 | { name: 'DVD Nightmare Symphony', price: 10, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=Nightmare+Symphony' },
86 | { name: 'DVD The Forgotten Cemetery', price: 60, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Forgotten+Cemetery' },
87 | { name: 'DVD The Haunted Carnival', price: 30, image: 'https://placehold.co/150/000000/FFFFFF/?font=oswald&text=The+Haunted+Carnival' },
88 | ];
89 |
90 | db.products.insertMany(products);
91 |
--------------------------------------------------------------------------------
/front-end/nginx.conf:
--------------------------------------------------------------------------------
1 | worker_processes auto;
2 | pid /tmp/nginx.pid;
3 |
4 | #daemon off;
5 |
6 | events {
7 | worker_connections 1024;
8 | }
9 |
10 | http {
11 |
12 | server_tokens off;
13 |
14 | sendfile on;
15 | keepalive_timeout 65;
16 |
17 | # Specify MIME types for files.
18 | include mime.types;
19 | default_type application/octet-stream;
20 |
21 | # Update charset_types to match updated mime.types.
22 | # text/html is always included by charset module.
23 | # Default: text/html text/xml text/plain text/vnd.wap.wml application/javascript application/rss+xml
24 | charset_types
25 | text/css
26 | text/plain
27 | text/vnd.wap.wml
28 | application/javascript
29 | application/json
30 | application/rss+xml
31 | application/xml;
32 |
33 | gzip on;
34 | gzip_comp_level 5;
35 | gzip_vary on;
36 |
37 | gzip_types
38 | application/atom+xml
39 | application/javascript
40 | application/json
41 | application/ld+json
42 | application/manifest+json
43 | application/rss+xml
44 | application/vnd.geo+json
45 | application/vnd.ms-fontobject
46 | application/x-font-ttf
47 | application/x-web-app-manifest+json
48 | application/xhtml+xml
49 | application/xml
50 | font/opentype
51 | image/bmp
52 | image/svg+xml
53 | image/x-icon
54 | text/cache-manifest
55 | text/css
56 | text/plain
57 | text/vcard
58 | text/vnd.rim.location.xloc
59 | text/vtt
60 | text/x-component
61 | text/x-cross-domain-policy;
62 |
63 | # Disable client body temp files
64 | client_body_temp_path /dev/null;
65 | client_max_body_size 0;
66 |
67 | # Disable proxy temp files
68 | proxy_temp_path /dev/null;
69 |
70 | # Disable fastcgi temp files
71 | fastcgi_temp_path /dev/null;
72 |
73 | # Disable uwsgi temp files
74 | uwsgi_temp_path /dev/null;
75 |
76 | # Disable scgi temp files
77 | scgi_temp_path /dev/null;
78 |
79 | server {
80 | listen 8080;
81 |
82 | location / {
83 | expires 7d;
84 | root /app/;
85 | }
86 |
87 | }
88 |
89 | server {
90 | listen 8443 ssl;
91 |
92 | # SSL certificate
93 | ssl_certificate /sslcerts/dev.crt;
94 | ssl_certificate_key /sslcerts/dev.key;
95 | ssl_certificate_cache max=1000 inactive=20s valid=1m;
96 |
97 | # SSL configuration
98 | ssl_protocols TLSv1.2 TLSv1.3;
99 | ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
100 | ssl_prefer_server_ciphers off;
101 |
102 | # SSL session settings
103 | ssl_session_cache shared:SSL:10m;
104 | ssl_session_timeout 1d;
105 | ssl_session_tickets off;
106 |
107 | # Security headers
108 | add_header Strict-Transport-Security "max-age=63072000" always;
109 | add_header X-Frame-Options DENY;
110 | add_header X-Content-Type-Options nosniff;
111 | add_header X-XSS-Protection "1; mode=block";
112 |
113 | location / {
114 | expires 7d;
115 | root /app/;
116 | }
117 |
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/front-end/ssl/dev.key:
--------------------------------------------------------------------------------
1 | -----BEGIN PRIVATE KEY-----
2 | MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDgbyNnb+ibMdwd
3 | lSV0EZYpRYoJnMHDx9Fa2VR5Ir+uwDW6KxSKP2oZ5XeX3261zYaxyKJaCzbr+8K/
4 | Dg6URkldg7IA7oApKt8LzCCn2Ct0ta0XovDGJCZbGBswdhACoWdMrLiqa3cLE/XX
5 | ZETzoPgpTcmoniKeeJcuytigehoAhq2BiNVbxclBrLn3DHCzLFbJ7tm1x0+APzUM
6 | M22M+RFz6ANCwZaOTDRAcERVO/HWWanasFZB/HwSP7DXL960c+kqWJVBj/2Bqb2h
7 | I5CWaSPAgcEFiC+dg+RApkT36StGgZZGCV6DRS/hQjv/QO1tW3UROG0URfWNfCui
8 | PO3v0ways+MWu+SR6XMJxvqwgRsBGbYon6jOClTRMJi3GvUhpBoOcBYpj0KJ26T0
9 | oP2iDIbvVA13eLSgHblOLEwwHlJQD+Slh3NgXTDRbwCTl3TcrFkkJPczHRb/PRAf
10 | zxyUbywH+YwUMkmHZKgWtve/5ZvUBxdEy2JqTjtrJ63e1allPXo6e4yx/zrdlUiO
11 | 9Wqfe54hBzqutEqxrhXfpY0vN+SnC6mL4ujlPi6FzkEJ2P5l1jDrmNjeTFS1oKXO
12 | FVFi9rA1OzyZ4676KCCMMsc4qw1HWDx2pZtujweuV2WMhQuE6hdNtb7GmVON9Lzn
13 | Xtp117CB3UxYeHWSqjZOcldVSl7yZQIDAQABAoICABQW1qYyXfGQWbnbKRZV4alR
14 | lGzOV4jJCMwXQoiyS9KeWWDP7N+AFCT7xYP2Z8BFxJ4Czcd+E/01FKZJ4ZOzo+sH
15 | OAZ90sJ8mCkD7VNg1ej1PyhruR4fS8ChLIPeuYmmvWqfVktI4ce/0mRkkQh2uufl
16 | ktyxRPuG4094sEHTFCNCauL6ptB3aI+koKPEvQzItf8OznWHQO5DmiG8s7xn6gBE
17 | PxB5aBrWJuIyumcfvcK4VWwiFyKCcW+nvFJSsl5YNsGH2tpaw3AZC8SR/KX+BMl9
18 | Q9QVL9/rV6HS0D8Zo7pErLkZqxhYd9PzvA+Z1fbRNi4jrR3gsj1nJTe5OPob21A2
19 | QEl0RrEpo7GdOn8NuNJVSbguN3yv5PpjXJtzFJA6bACDUaWmtvm0AJj8xnmcCWLD
20 | 0OZWPNDHI+95KH4DnYdTdoXywJ6So8cu5gbjOpDnMcL6jMTEP212k96ktBcvfRXl
21 | S5TC24o0u/xMFGCYiwxLo/7Olvq+Y8wnr9K2nkYrT6wZtmp+/bpjioEVoESQHAfE
22 | a2c1yISyLNZRSdplalQMFAIOZEOGwPD4awooNxUF62hGVsyY6boC3LUepeSTlr37
23 | /lH7fK9ivjVDP4yKvjQrSKDOMLPRZ6LWxUXaadM9mr7HESTJ7yXzzs7Y0UOgoLG8
24 | fXKBmqmQgms4HjimJZ/ZAoIBAQDyf9SJcmxPFi6cFjzxoNujU54Ug7chBpp1VPV0
25 | 5PIhi2RbOHEPKBjQDX2BVe3Pk7EqOm5KJagQ+Lr2lHcgvzb8EUWaQc8TEqT1bM76
26 | GPSPgtZMOS2joRxCqzewhRkDl/wDgsws6Aqs6X+UWnO3+ZGyEe5Hqxv+L3JZvMVM
27 | fd1ocHxshVsyXIN/hz7PPJ6Lxra9OXrD9JXgDbqODc+8js6PItDj+yjAz0bkysRd
28 | rzAa132p2Lird/XP2bbrWXZBfsR88y0L+KufFfiyXwg87Gd8DlqubG4LlVbSCfM9
29 | XXjOCahdjaiHTD56HPfwwx4NmBjs4vfs1HPQkPUPz4HprcupAoIBAQDs7dZwA8kN
30 | EHyl2kgx88n3r7VCKVZvCtgNQKT688up3cvmJKAOs1ubvdMqMhb+Yd3aakiYTRgx
31 | NCYxwkaeIhtm0A0TbgQMzM4FPz9qd20w0+V4IrHZgKJyLk3ZzkPwqrYpAhbaWZ5O
32 | 5G9xOSuKFrI8vp9bITcaKUCEBHrjGcG87yXklqy4Mgv1ZrC06LT0fCjXzb8NpCXV
33 | XJ7L+NXaX05fIfjTjxMwjp4VuQ7uoI3qjGcaq2bkkNHmefvO7NHLP/gaRsOjlqFM
34 | +nkUbepEWs9HEqrDBe/Zc3zY/7XgpnVOOngAGR8+y2M0uYt9fbZtI+HSwvKBm1S3
35 | MXn1y2aRhgZdAoIBAFifjP4aWijhE3Gp0uX/gqWC9p/Kr0+biTFeNkJdNXw65BdO
36 | XktKvR6z/20YCUw2TqX/KrVgY7aoIDPjeFH4b9DZQxWn/VScGLs048OVATO9fjHn
37 | J53IUHqw9i5FximBDvH9iD7VIqmkwCJomCfhRnTMhsecaiOQFXtkOLrGuPblPSOv
38 | 7O9PziL6O2MjZVHx8nDfI9WfenKisc4ekA/6JNU8xRi8fl1QozkUPYV7c9orbNp4
39 | wKyxr50hlO8YB9bRzF9Zw9m/LIF8VyJhBrK1NvCokk1yhRkon80tI78GfRLXhreC
40 | ANhJbf/UvGw58q7HKjzQigj8b8MGZvk7PeA3ytkCggEAWrkr3pKkftssT7nI4U/P
41 | q4ddqer4TObjGhgB1JJcDzx94i9uFpL5ZiaG4GZzxY6mDAecqq0CsXydj3or3kuD
42 | IJhUGXeMF3apAPtaBiOa7RKGkL0ASx/llEFE0D0aaOKFhChnpc6hPuZcAgf5MxRD
43 | xdzcWmHanqrjKGoqwCFRvU35vZEwrAuF5r8etPdSqzeL0C7ZlJihYBTqPYKYKNlL
44 | wkPY6vGjUHmb8Qd+v0M9dG204VJ2wNjvdwiIspCeOOzSzQUBzvdT5o0Wpgr8pJX9
45 | PuLX/nucfzCowtMPyukSjBT5dFgqTbpFz5+o2TG1FA+kXjcF8zkOdGN+pu7u5JRk
46 | hQKCAQEA0d3FchlkzPr+mejs+PlO4a5YhZ7KDJYUdr4FX1BwWZLbeTyVOT9LqWQW
47 | 5A93OVzU1qvjzLTmCLfln2zZeD+gM2Xf5vNXgztRKlvPbIvVSL7j9qfeeHp+1QF3
48 | zIWBvPnF4U32SApyJoFQHG/7tXINbwua+H5zb+Onnt4dRRA1cx+Al+9sDeB7ZwrL
49 | 9voJhTtHUBukUIsGuqPJ4NvmppZvWarZMo4Uo48lVvD0ajzT3+E1i1QOYK04jDL0
50 | k3qLDIXjfFgj+dZD6PdNsywsjz/v5buqUC/eWsyNwb1w6TwekfFOOiZGlv8jjBT2
51 | SIaNB2vGANajqN0br6/2esTDUuFtwQ==
52 | -----END PRIVATE KEY-----
53 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | front-end:
4 | build:
5 | context: front-end
6 | target: final
7 | args:
8 | VUE_APP_BASE_URL: http://localhost:3000/
9 | image: gustavoapolinario/ecommerce-lab-front-end
10 | ports:
11 | - "8080:8080"
12 | - "8443:8443"
13 |
14 | product-api:
15 | build:
16 | context: product-api
17 | image: gustavoapolinario/ecommerce-lab-product-api
18 | environment:
19 | PORT: 3000
20 | MONGODB_ENDPOINT: mongo
21 | MONGODB_PORT: 27017
22 | MONGODB_USERNAME: product-api
23 | MONGODB_PASSWORD: product-api-password
24 | MONGODB_DATABASE: products
25 | REDIS_ENDPOINT: redis
26 | REDIS_PORT: 6379
27 | REDIS_USERNAME: default
28 | REDIS_PASSWORD: your_redis_password
29 | ports:
30 | - "3000:3000"
31 | depends_on:
32 | - mongo
33 | - redis
34 |
35 | buy-api:
36 | build:
37 | context: buy-api
38 | image: gustavoapolinario/ecommerce-lab-buy-api
39 | environment:
40 | PORT: 3001
41 | PRODUCT_API_URL: http://product-api:3000
42 | RABBITMQ_ENDPOINT: rabbitmq
43 | RABBITMQ_PORT: 5672
44 | RABBITMQ_USERNAME: rabbitmq
45 | RABBITMQ_PASSWORD: rabbitmq-password
46 | REDIS_ENDPOINT: redis
47 | REDIS_PORT: 6379
48 | REDIS_USERNAME: default
49 | REDIS_PASSWORD: your_redis_password
50 | ports:
51 | - "3001:3001"
52 | depends_on:
53 | - rabbitmq
54 | - redis
55 |
56 | buy-job:
57 | build:
58 | context: buy-job
59 | image: gustavoapolinario/ecommerce-lab-buy-job
60 | environment:
61 | PORT: 3002
62 | RABBITMQ_ENDPOINT: rabbitmq
63 | RABBITMQ_PORT: 5672
64 | RABBITMQ_USERNAME: rabbitmq
65 | RABBITMQ_PASSWORD: rabbitmq-password
66 | KAFKA_ENDPOINT: kafka
67 | KAFKA_PORT: 9092
68 | ports:
69 | - "3002:3002"
70 | depends_on:
71 | - rabbitmq
72 | - kafka
73 |
74 | mongo:
75 | image: mongo
76 | restart: always
77 | volumes:
78 | - ./mongodb/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
79 | - mongo-data:/data/db
80 |
81 | redis:
82 | image: redis:latest
83 | command: ["redis-server", "--requirepass", "your_redis_password"]
84 | ports:
85 | - "6379:6379"
86 | volumes:
87 | - redis-data:/data
88 |
89 | rabbitmq:
90 | image: rabbitmq:3-management
91 | ports:
92 | - "5672:5672" # RabbitMQ main port
93 | - "15672:15672" # RabbitMQ management plugin port
94 | environment:
95 | RABBITMQ_DEFAULT_USER: rabbitmq
96 | RABBITMQ_DEFAULT_PASS: rabbitmq-password
97 | volumes:
98 | - rabbitmq_data:/var/lib/rabbitmq
99 |
100 | zookeeper:
101 | image: confluentinc/cp-zookeeper:latest
102 | environment:
103 | ALLOW_ANONYMOUS_LOGIN: yes
104 | ZOOKEEPER_CLIENT_PORT: 2181
105 | ZOOKEEPER_TICK_TIME: 2000
106 | ports:
107 | - "2181:2181"
108 |
109 | kafka:
110 | image: confluentinc/cp-kafka:latest
111 | depends_on:
112 | - zookeeper
113 | ports:
114 | - "9092:9092"
115 | environment:
116 | KAFKA_BROKER_ID: 1
117 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
118 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
119 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
120 | # ALLOW_PLAINTEXT_LISTENER: yes
121 |
122 | kafka-ui:
123 | image: provectuslabs/kafka-ui:latest
124 | depends_on:
125 | - kafka
126 | ports:
127 | - "7070:8080"
128 | environment:
129 | KAFKA_CLUSTERS_0_NAME: kafka
130 | KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
131 | KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181
132 |
133 |
134 |
135 | volumes:
136 | mongo-data:
137 | redis-data:
138 | rabbitmq_data:
139 |
140 | networks:
141 | ecommercelab:
142 | driver: bridge
143 |
--------------------------------------------------------------------------------
/docker-compose-dev.yml:
--------------------------------------------------------------------------------
1 | services:
2 |
3 | front-end:
4 | image: node:20
5 | working_dir: /app
6 | environment:
7 | VUE_APP_BASE_URL: http://localhost:3000/
8 | PORT: 8080
9 | ports:
10 | - "8080:8080"
11 | volumes:
12 | - ./front-end/:/app/
13 | command: [ "npm", "run", "serve" ]
14 |
15 | # Test buy product:
16 | # curl localhost:3000/products/
17 | # curl localhost:3000/products/$(curl -s localhost:3000/products/ | jq -r '.[0]._id')
18 | product-api:
19 | image: node:20
20 | working_dir: /app
21 | environment:
22 | PORT: 3000
23 | MONGODB_ENDPOINT: mongo
24 | MONGODB_PORT: 27017
25 | MONGODB_USERNAME: product-api
26 | MONGODB_PASSWORD: product-api-password
27 | MONGODB_DATABASE: products
28 | REDIS_ENDPOINT: redis
29 | REDIS_PORT: 6379
30 | REDIS_USERNAME: default
31 | REDIS_PASSWORD: your_redis_password
32 | ports:
33 | - "3000:3000"
34 | volumes:
35 | - ./product-api/:/app/
36 | command: [ "npm", "run", "dev" ]
37 | depends_on:
38 | - mongo
39 | - redis
40 |
41 | # Test buy product:
42 | # curl -X POST localhost:3001/buy/$(curl -s localhost:3000/products/ | jq -r '.[0]._id')
43 | buy-api:
44 | image: node:20
45 | working_dir: /app
46 | environment:
47 | PORT: 3001
48 | PRODUCT_API_URL: http://product-api:3000
49 | RABBITMQ_ENDPOINT: rabbitmq
50 | RABBITMQ_PORT: 5672
51 | RABBITMQ_USERNAME: rabbitmq
52 | RABBITMQ_PASSWORD: rabbitmq-password
53 | REDIS_ENDPOINT: redis
54 | REDIS_PORT: 6379
55 | REDIS_USERNAME: default
56 | REDIS_PASSWORD: your_redis_password
57 | ports:
58 | - "3001:3001"
59 | volumes:
60 | - ./buy-api/:/app/
61 | command: [ "npm", "run", "dev" ]
62 | depends_on:
63 | - rabbitmq
64 | - redis
65 |
66 | buy-job:
67 | image: node:20
68 | working_dir: /app
69 | environment:
70 | PORT: 3002
71 | RABBITMQ_ENDPOINT: rabbitmq
72 | RABBITMQ_PORT: 5672
73 | RABBITMQ_USERNAME: rabbitmq
74 | RABBITMQ_PASSWORD: rabbitmq-password
75 | KAFKA_ENDPOINT: kafka
76 | KAFKA_PORT: 9092
77 | ports:
78 | - "3002:3002"
79 | volumes:
80 | - ./buy-job/:/app/
81 | command: [ "npm", "run", "dev" ]
82 | depends_on:
83 | - rabbitmq
84 | - kafka
85 |
86 | mongo:
87 | image: mongo
88 | restart: always
89 | volumes:
90 | - ./mongodb/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
91 | - mongo-data:/data/db
92 |
93 | redis:
94 | image: redis:latest
95 | command: ["redis-server", "--requirepass", "your_redis_password"]
96 | ports:
97 | - "6379:6379"
98 | volumes:
99 | - redis-data:/data
100 |
101 | rabbitmq:
102 | image: rabbitmq:3-management
103 | ports:
104 | - "5672:5672" # RabbitMQ main port
105 | - "15672:15672" # RabbitMQ management plugin port
106 | environment:
107 | RABBITMQ_DEFAULT_USER: rabbitmq
108 | RABBITMQ_DEFAULT_PASS: rabbitmq-password
109 | volumes:
110 | - rabbitmq_data:/var/lib/rabbitmq
111 |
112 |
113 |
114 | zookeeper:
115 | image: confluentinc/cp-zookeeper:latest
116 | environment:
117 | ALLOW_ANONYMOUS_LOGIN: yes
118 | ZOOKEEPER_CLIENT_PORT: 2181
119 | ZOOKEEPER_TICK_TIME: 2000
120 | ports:
121 | - "2181:2181"
122 |
123 | kafka:
124 | image: confluentinc/cp-kafka:latest
125 | depends_on:
126 | - zookeeper
127 | ports:
128 | - "9092:9092"
129 | environment:
130 | KAFKA_BROKER_ID: 1
131 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
132 | KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
133 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
134 | # ALLOW_PLAINTEXT_LISTENER: yes
135 |
136 | kafka-ui:
137 | image: provectuslabs/kafka-ui:latest
138 | depends_on:
139 | - kafka
140 | ports:
141 | - "7070:8080"
142 | environment:
143 | KAFKA_CLUSTERS_0_NAME: kafka
144 | KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9092
145 | KAFKA_CLUSTERS_0_ZOOKEEPER: zookeeper:2181
146 |
147 |
148 |
149 | volumes:
150 | mongo-data:
151 | redis-data:
152 | rabbitmq_data:
153 |
154 | networks:
155 | ecommercelab:
156 | driver: bridge
157 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # About
2 |
3 | This project was created to test and refresh my skills wih:
4 |
5 | - Backend node.js
6 | - Front Vue.js
7 | - Document Database mongoDB
8 | - Queue with RabbitMQ
9 | - Events with Kafka
10 |
11 | ## How it works
12 |
13 | 
14 |
15 | ### Front end
16 |
17 | It has a simple front-end with Vue.js. The Vue generates a static page who list all products requesting the product-api.
18 |
19 | ### Product API
20 |
21 | The product API is a node.js service with:
22 |
23 | - Express to create the API
24 | - Mongoose to manage the Document Database for Products
25 | - Redis for Cache the responses
26 |
27 | ### Apache benchmark-test
28 |
29 | Apache Benchmark Test is a tool who makes a benchmark to test the time spend on requests.
30 |
31 | Verify the README on this folder to run and make your own tests
32 |
33 | ### Buy-api
34 |
35 | On the list of products, you can click to buy the product (fake, of course).
36 |
37 | When click on it, the buy-api will receive the request and save the information of product bought on a Queue in RabbitMQ.
38 |
39 | ### Buy-job
40 |
41 | This project receive the product bought in Queue and process it.
42 |
43 | After bought, service generates an Event on Kafka.
44 |
45 |
46 |
47 |
48 |
49 | # Docker compose
50 |
51 | To dev and test the applications, I created a docker compose configuration.
52 |
53 | The docker compose start the environment with all envs who the apps need.
54 |
55 | ## Dev Environment
56 |
57 | ```bash
58 | docker compose -f docker-compose-dev.yml up
59 | ```
60 |
61 | ## Local tests with image builds
62 |
63 | ```bash
64 | docker compose up
65 | ```
66 |
67 | ## How to test
68 |
69 | http://localhost:8080/
70 |
71 | ## Logs
72 |
73 | ```bash
74 | docker compose logs -f
75 | ```
76 |
77 |
78 | ## Container Image
79 |
80 | ### Build Image
81 |
82 | ```bash
83 | docker compose build
84 | ```
85 |
86 | or
87 |
88 | ```bash
89 | docker compose build [NAME]
90 | ex: docker compose build front-end
91 | ```
92 |
93 |
94 | ### Push Image
95 |
96 | ```bash
97 | docker compose push
98 | ```
99 |
100 | or
101 |
102 | ```bash
103 | docker compose push [NAME]
104 | ex: docker compose push front-end
105 | ```
106 |
107 |
108 |
109 |
110 |
111 | # Minikube
112 |
113 | ## Start Minikube
114 |
115 | ```bash
116 | minikube start
117 | minikube dashboard
118 | ```
119 |
120 | ## Install Databases
121 |
122 | ```bash
123 | helm repo add bitnami https://charts.bitnami.com/bitnami
124 | helm repo update
125 | kubectl apply -f gitops/databases-helm/mongodb/mongodb-configmap.yml
126 | helm upgrade --install mongo bitnami/mongodb \
127 | --set auth.username=product-api \
128 | --set auth.password=product-api-password \
129 | --set auth.database=products \
130 | --set persistence.size=1Gi \
131 | --set initdbScriptsConfigMap=mongo-init-configmap \
132 | --version 15.6.9
133 |
134 | helm upgrade --install redis bitnami/redis \
135 | --set global.redis.password=your_redis_password \
136 | --set master.persistence.size=300Mi \
137 | --set replica.persistence.size=300Mi \
138 | --set replica.replicaCount=1 \
139 | --version 19.5.5
140 |
141 | helm upgrade --install rabbitmq bitnami/rabbitmq \
142 | --set auth.username=rabbitmq \
143 | --set auth.password=rabbitmq-password \
144 | --set persistence.size=1Gi \
145 | --version 14.4.4
146 |
147 | helm upgrade --install kafka bitnami/kafka \
148 | --set auth.enabled=false \
149 | --set controller.persistence.size=1Gi \
150 | --set zookeeper.persistence.size=1Gi \
151 | --set controller.replicaCount=1 \
152 | --set zookeeper.replicaCount=1 \
153 | --set listeners.client.protocol=PLAINTEXT \
154 | --version 29.3.4
155 |
156 | helm repo add kafka-ui https://provectus.github.io/kafka-ui-charts
157 | helm install kafka-ui kafka-ui/kafka-ui \
158 | --set kafka.clusters[0].name=kafka \
159 | --set kafka.clusters[0].bootstrapServers=kafka:9092 \
160 | --set auth.type=disabled \
161 | --set management.health.ldap.enabled=false
162 | ```
163 |
164 | ## Install microservices
165 |
166 | ```bash
167 | kubectl apply -f gitops/apps/
168 | ```
169 |
170 | ## Test applications
171 |
172 | ```bash
173 | kubectl port-forward svc/front-end-service 8080 &
174 | kubectl port-forward svc/product-api-service 3000 &
175 | kubectl port-forward svc/buy-api-service 3001 &
176 | kubectl port-forward svc/eccomerce-lab-kafka-ui 9080:80 &
177 | ```
178 |
179 |
180 | ## Verify Mongodb Data
181 |
182 | ```bash
183 | kubectl run -i --tty --rm mongo-client --image=mongo --restart=Never -- bash
184 | mongosh "mongodb://product-api:product-api-password@mongo-mongodb:27017/products"
185 | db.products.find().pretty()
186 | ```
187 |
188 | ## Verify Redis Data
189 |
190 | ```bash
191 | kubectl run -i --tty --rm redis-client --image=redis --restart=Never -- bash
192 | redis-cli -h redis-master -a your_redis_password
193 | KEYS *
194 | ```
195 |
196 | ## Clean up
197 |
198 | ```bash
199 | kubectl delete -f gitops/apps/
200 | helm uninstall mongo
201 | helm uninstall redis
202 | helm uninstall rabbitmq
203 | helm uninstall kafka bitnami/kafka
204 | helm uninstall kafka-ui
205 | kubectl delete -f gitops/databases-helm/mongodb/mongodb-configmap.yml
206 | ```
207 |
208 |
209 |
210 |
211 |
212 | # GitOps with ArgoCD
213 |
214 | ## Start Minikube
215 |
216 | ```bash
217 | minikube start
218 | minikube dashboard
219 | ```
220 |
221 | ## Install ArgoCD
222 |
223 | ```bash
224 | kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/v2.5.8/manifests/install.yaml
225 | ```
226 |
227 | ## Acess the UI
228 |
229 | ```bash
230 | kubectl port-forward svc/argocd-server -n argocd 9090:443
231 | ```
232 |
233 | ## Get Admin Password
234 |
235 | ```bash
236 | kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo
237 | ```
238 |
239 | ## Install databases
240 |
241 | The folder gitops/databases-helm/ have all ArgoCD Application needed to install all databases.
242 |
243 | ```bash
244 | kubectl apply -f gitops/databases-helm/kafka/
245 | kubectl apply -f gitops/databases-helm/kafka-ui/
246 | kubectl apply -f gitops/databases-helm/mongodb/
247 | kubectl apply -f gitops/databases-helm/rabbitmq/
248 | kubectl apply -f gitops/databases-helm/redis/
249 | ```
250 |
251 | ## Install microservices
252 |
253 | To install the apps, We will use the ApplicationSet. It will get all configuration from the github automatically
254 |
255 | ```bash
256 | argocd appset create gitops/ArgoCD-apps.yml --port-forward --port-forward-namespace argocd
257 | ```
258 |
259 | ## Clean up
260 |
261 | ```bash
262 | argocd app delete argocd/eccomerce-lab-redis --cascade --port-forward --port-forward-namespace argocd
263 | argocd app delete argocd/eccomerce-lab-rabbitmq --cascade --port-forward --port-forward-namespace argocd
264 | argocd app delete argocd/eccomerce-lab-kafka --cascade --port-forward --port-forward-namespace argocd
265 | argocd app delete argocd/eccomerce-lab-kafka-ui --cascade --port-forward --port-forward-namespace argocd
266 | argocd app delete argocd/eccomerce-lab-mongodb --cascade --port-forward --port-forward-namespace argocd
267 | argocd appset delete eccomerce-lab-apps
268 | ```
269 |
--------------------------------------------------------------------------------
/ecommerce-lab.drawio:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/buy-job/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "buy-job",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "buy-job",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "amqplib": "^0.10.4",
13 | "express": "^4.19.2",
14 | "kafkajs": "^2.2.4"
15 | },
16 | "devDependencies": {
17 | "nodemon": "^3.1.3"
18 | }
19 | },
20 | "node_modules/@acuminous/bitsyntax": {
21 | "version": "0.1.2",
22 | "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz",
23 | "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==",
24 | "dependencies": {
25 | "buffer-more-ints": "~1.0.0",
26 | "debug": "^4.3.4",
27 | "safe-buffer": "~5.1.2"
28 | },
29 | "engines": {
30 | "node": ">=0.8"
31 | }
32 | },
33 | "node_modules/accepts": {
34 | "version": "1.3.8",
35 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
36 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
37 | "dependencies": {
38 | "mime-types": "~2.1.34",
39 | "negotiator": "0.6.3"
40 | },
41 | "engines": {
42 | "node": ">= 0.6"
43 | }
44 | },
45 | "node_modules/amqplib": {
46 | "version": "0.10.4",
47 | "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.4.tgz",
48 | "integrity": "sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==",
49 | "dependencies": {
50 | "@acuminous/bitsyntax": "^0.1.2",
51 | "buffer-more-ints": "~1.0.0",
52 | "readable-stream": "1.x >=1.1.9",
53 | "url-parse": "~1.5.10"
54 | },
55 | "engines": {
56 | "node": ">=10"
57 | }
58 | },
59 | "node_modules/anymatch": {
60 | "version": "3.1.3",
61 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
62 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
63 | "dev": true,
64 | "dependencies": {
65 | "normalize-path": "^3.0.0",
66 | "picomatch": "^2.0.4"
67 | },
68 | "engines": {
69 | "node": ">= 8"
70 | }
71 | },
72 | "node_modules/array-flatten": {
73 | "version": "1.1.1",
74 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
75 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
76 | },
77 | "node_modules/balanced-match": {
78 | "version": "1.0.2",
79 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
80 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
81 | "dev": true
82 | },
83 | "node_modules/binary-extensions": {
84 | "version": "2.3.0",
85 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
86 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
87 | "dev": true,
88 | "engines": {
89 | "node": ">=8"
90 | },
91 | "funding": {
92 | "url": "https://github.com/sponsors/sindresorhus"
93 | }
94 | },
95 | "node_modules/body-parser": {
96 | "version": "1.20.2",
97 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
98 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
99 | "dependencies": {
100 | "bytes": "3.1.2",
101 | "content-type": "~1.0.5",
102 | "debug": "2.6.9",
103 | "depd": "2.0.0",
104 | "destroy": "1.2.0",
105 | "http-errors": "2.0.0",
106 | "iconv-lite": "0.4.24",
107 | "on-finished": "2.4.1",
108 | "qs": "6.11.0",
109 | "raw-body": "2.5.2",
110 | "type-is": "~1.6.18",
111 | "unpipe": "1.0.0"
112 | },
113 | "engines": {
114 | "node": ">= 0.8",
115 | "npm": "1.2.8000 || >= 1.4.16"
116 | }
117 | },
118 | "node_modules/body-parser/node_modules/debug": {
119 | "version": "2.6.9",
120 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
121 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
122 | "dependencies": {
123 | "ms": "2.0.0"
124 | }
125 | },
126 | "node_modules/body-parser/node_modules/ms": {
127 | "version": "2.0.0",
128 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
129 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
130 | },
131 | "node_modules/brace-expansion": {
132 | "version": "1.1.11",
133 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
134 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
135 | "dev": true,
136 | "dependencies": {
137 | "balanced-match": "^1.0.0",
138 | "concat-map": "0.0.1"
139 | }
140 | },
141 | "node_modules/braces": {
142 | "version": "3.0.3",
143 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
144 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
145 | "dev": true,
146 | "dependencies": {
147 | "fill-range": "^7.1.1"
148 | },
149 | "engines": {
150 | "node": ">=8"
151 | }
152 | },
153 | "node_modules/buffer-more-ints": {
154 | "version": "1.0.0",
155 | "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
156 | "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
157 | },
158 | "node_modules/bytes": {
159 | "version": "3.1.2",
160 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
161 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
162 | "engines": {
163 | "node": ">= 0.8"
164 | }
165 | },
166 | "node_modules/call-bind": {
167 | "version": "1.0.7",
168 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
169 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
170 | "dependencies": {
171 | "es-define-property": "^1.0.0",
172 | "es-errors": "^1.3.0",
173 | "function-bind": "^1.1.2",
174 | "get-intrinsic": "^1.2.4",
175 | "set-function-length": "^1.2.1"
176 | },
177 | "engines": {
178 | "node": ">= 0.4"
179 | },
180 | "funding": {
181 | "url": "https://github.com/sponsors/ljharb"
182 | }
183 | },
184 | "node_modules/chokidar": {
185 | "version": "3.6.0",
186 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
187 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
188 | "dev": true,
189 | "dependencies": {
190 | "anymatch": "~3.1.2",
191 | "braces": "~3.0.2",
192 | "glob-parent": "~5.1.2",
193 | "is-binary-path": "~2.1.0",
194 | "is-glob": "~4.0.1",
195 | "normalize-path": "~3.0.0",
196 | "readdirp": "~3.6.0"
197 | },
198 | "engines": {
199 | "node": ">= 8.10.0"
200 | },
201 | "funding": {
202 | "url": "https://paulmillr.com/funding/"
203 | },
204 | "optionalDependencies": {
205 | "fsevents": "~2.3.2"
206 | }
207 | },
208 | "node_modules/concat-map": {
209 | "version": "0.0.1",
210 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
211 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
212 | "dev": true
213 | },
214 | "node_modules/content-disposition": {
215 | "version": "0.5.4",
216 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
217 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
218 | "dependencies": {
219 | "safe-buffer": "5.2.1"
220 | },
221 | "engines": {
222 | "node": ">= 0.6"
223 | }
224 | },
225 | "node_modules/content-disposition/node_modules/safe-buffer": {
226 | "version": "5.2.1",
227 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
228 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
229 | "funding": [
230 | {
231 | "type": "github",
232 | "url": "https://github.com/sponsors/feross"
233 | },
234 | {
235 | "type": "patreon",
236 | "url": "https://www.patreon.com/feross"
237 | },
238 | {
239 | "type": "consulting",
240 | "url": "https://feross.org/support"
241 | }
242 | ]
243 | },
244 | "node_modules/content-type": {
245 | "version": "1.0.5",
246 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
247 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
248 | "engines": {
249 | "node": ">= 0.6"
250 | }
251 | },
252 | "node_modules/cookie": {
253 | "version": "0.6.0",
254 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
255 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
256 | "engines": {
257 | "node": ">= 0.6"
258 | }
259 | },
260 | "node_modules/cookie-signature": {
261 | "version": "1.0.6",
262 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
263 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
264 | },
265 | "node_modules/core-util-is": {
266 | "version": "1.0.3",
267 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
268 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
269 | },
270 | "node_modules/debug": {
271 | "version": "4.3.5",
272 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
273 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
274 | "dependencies": {
275 | "ms": "2.1.2"
276 | },
277 | "engines": {
278 | "node": ">=6.0"
279 | },
280 | "peerDependenciesMeta": {
281 | "supports-color": {
282 | "optional": true
283 | }
284 | }
285 | },
286 | "node_modules/define-data-property": {
287 | "version": "1.1.4",
288 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
289 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
290 | "dependencies": {
291 | "es-define-property": "^1.0.0",
292 | "es-errors": "^1.3.0",
293 | "gopd": "^1.0.1"
294 | },
295 | "engines": {
296 | "node": ">= 0.4"
297 | },
298 | "funding": {
299 | "url": "https://github.com/sponsors/ljharb"
300 | }
301 | },
302 | "node_modules/depd": {
303 | "version": "2.0.0",
304 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
305 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
306 | "engines": {
307 | "node": ">= 0.8"
308 | }
309 | },
310 | "node_modules/destroy": {
311 | "version": "1.2.0",
312 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
313 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
314 | "engines": {
315 | "node": ">= 0.8",
316 | "npm": "1.2.8000 || >= 1.4.16"
317 | }
318 | },
319 | "node_modules/ee-first": {
320 | "version": "1.1.1",
321 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
322 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
323 | },
324 | "node_modules/encodeurl": {
325 | "version": "1.0.2",
326 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
327 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
328 | "engines": {
329 | "node": ">= 0.8"
330 | }
331 | },
332 | "node_modules/es-define-property": {
333 | "version": "1.0.0",
334 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
335 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
336 | "dependencies": {
337 | "get-intrinsic": "^1.2.4"
338 | },
339 | "engines": {
340 | "node": ">= 0.4"
341 | }
342 | },
343 | "node_modules/es-errors": {
344 | "version": "1.3.0",
345 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
346 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
347 | "engines": {
348 | "node": ">= 0.4"
349 | }
350 | },
351 | "node_modules/escape-html": {
352 | "version": "1.0.3",
353 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
354 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
355 | },
356 | "node_modules/etag": {
357 | "version": "1.8.1",
358 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
359 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
360 | "engines": {
361 | "node": ">= 0.6"
362 | }
363 | },
364 | "node_modules/express": {
365 | "version": "4.19.2",
366 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
367 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
368 | "dependencies": {
369 | "accepts": "~1.3.8",
370 | "array-flatten": "1.1.1",
371 | "body-parser": "1.20.2",
372 | "content-disposition": "0.5.4",
373 | "content-type": "~1.0.4",
374 | "cookie": "0.6.0",
375 | "cookie-signature": "1.0.6",
376 | "debug": "2.6.9",
377 | "depd": "2.0.0",
378 | "encodeurl": "~1.0.2",
379 | "escape-html": "~1.0.3",
380 | "etag": "~1.8.1",
381 | "finalhandler": "1.2.0",
382 | "fresh": "0.5.2",
383 | "http-errors": "2.0.0",
384 | "merge-descriptors": "1.0.1",
385 | "methods": "~1.1.2",
386 | "on-finished": "2.4.1",
387 | "parseurl": "~1.3.3",
388 | "path-to-regexp": "0.1.7",
389 | "proxy-addr": "~2.0.7",
390 | "qs": "6.11.0",
391 | "range-parser": "~1.2.1",
392 | "safe-buffer": "5.2.1",
393 | "send": "0.18.0",
394 | "serve-static": "1.15.0",
395 | "setprototypeof": "1.2.0",
396 | "statuses": "2.0.1",
397 | "type-is": "~1.6.18",
398 | "utils-merge": "1.0.1",
399 | "vary": "~1.1.2"
400 | },
401 | "engines": {
402 | "node": ">= 0.10.0"
403 | }
404 | },
405 | "node_modules/express/node_modules/debug": {
406 | "version": "2.6.9",
407 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
408 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
409 | "dependencies": {
410 | "ms": "2.0.0"
411 | }
412 | },
413 | "node_modules/express/node_modules/ms": {
414 | "version": "2.0.0",
415 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
416 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
417 | },
418 | "node_modules/express/node_modules/safe-buffer": {
419 | "version": "5.2.1",
420 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
421 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
422 | "funding": [
423 | {
424 | "type": "github",
425 | "url": "https://github.com/sponsors/feross"
426 | },
427 | {
428 | "type": "patreon",
429 | "url": "https://www.patreon.com/feross"
430 | },
431 | {
432 | "type": "consulting",
433 | "url": "https://feross.org/support"
434 | }
435 | ]
436 | },
437 | "node_modules/fill-range": {
438 | "version": "7.1.1",
439 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
440 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
441 | "dev": true,
442 | "dependencies": {
443 | "to-regex-range": "^5.0.1"
444 | },
445 | "engines": {
446 | "node": ">=8"
447 | }
448 | },
449 | "node_modules/finalhandler": {
450 | "version": "1.2.0",
451 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
452 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
453 | "dependencies": {
454 | "debug": "2.6.9",
455 | "encodeurl": "~1.0.2",
456 | "escape-html": "~1.0.3",
457 | "on-finished": "2.4.1",
458 | "parseurl": "~1.3.3",
459 | "statuses": "2.0.1",
460 | "unpipe": "~1.0.0"
461 | },
462 | "engines": {
463 | "node": ">= 0.8"
464 | }
465 | },
466 | "node_modules/finalhandler/node_modules/debug": {
467 | "version": "2.6.9",
468 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
469 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
470 | "dependencies": {
471 | "ms": "2.0.0"
472 | }
473 | },
474 | "node_modules/finalhandler/node_modules/ms": {
475 | "version": "2.0.0",
476 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
477 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
478 | },
479 | "node_modules/forwarded": {
480 | "version": "0.2.0",
481 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
482 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
483 | "engines": {
484 | "node": ">= 0.6"
485 | }
486 | },
487 | "node_modules/fresh": {
488 | "version": "0.5.2",
489 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
490 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
491 | "engines": {
492 | "node": ">= 0.6"
493 | }
494 | },
495 | "node_modules/fsevents": {
496 | "version": "2.3.3",
497 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
498 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
499 | "dev": true,
500 | "hasInstallScript": true,
501 | "optional": true,
502 | "os": [
503 | "darwin"
504 | ],
505 | "engines": {
506 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
507 | }
508 | },
509 | "node_modules/function-bind": {
510 | "version": "1.1.2",
511 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
512 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
513 | "funding": {
514 | "url": "https://github.com/sponsors/ljharb"
515 | }
516 | },
517 | "node_modules/get-intrinsic": {
518 | "version": "1.2.4",
519 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
520 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
521 | "dependencies": {
522 | "es-errors": "^1.3.0",
523 | "function-bind": "^1.1.2",
524 | "has-proto": "^1.0.1",
525 | "has-symbols": "^1.0.3",
526 | "hasown": "^2.0.0"
527 | },
528 | "engines": {
529 | "node": ">= 0.4"
530 | },
531 | "funding": {
532 | "url": "https://github.com/sponsors/ljharb"
533 | }
534 | },
535 | "node_modules/glob-parent": {
536 | "version": "5.1.2",
537 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
538 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
539 | "dev": true,
540 | "dependencies": {
541 | "is-glob": "^4.0.1"
542 | },
543 | "engines": {
544 | "node": ">= 6"
545 | }
546 | },
547 | "node_modules/gopd": {
548 | "version": "1.0.1",
549 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
550 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
551 | "dependencies": {
552 | "get-intrinsic": "^1.1.3"
553 | },
554 | "funding": {
555 | "url": "https://github.com/sponsors/ljharb"
556 | }
557 | },
558 | "node_modules/has-flag": {
559 | "version": "3.0.0",
560 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
561 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
562 | "dev": true,
563 | "engines": {
564 | "node": ">=4"
565 | }
566 | },
567 | "node_modules/has-property-descriptors": {
568 | "version": "1.0.2",
569 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
570 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
571 | "dependencies": {
572 | "es-define-property": "^1.0.0"
573 | },
574 | "funding": {
575 | "url": "https://github.com/sponsors/ljharb"
576 | }
577 | },
578 | "node_modules/has-proto": {
579 | "version": "1.0.3",
580 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
581 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
582 | "engines": {
583 | "node": ">= 0.4"
584 | },
585 | "funding": {
586 | "url": "https://github.com/sponsors/ljharb"
587 | }
588 | },
589 | "node_modules/has-symbols": {
590 | "version": "1.0.3",
591 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
592 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
593 | "engines": {
594 | "node": ">= 0.4"
595 | },
596 | "funding": {
597 | "url": "https://github.com/sponsors/ljharb"
598 | }
599 | },
600 | "node_modules/hasown": {
601 | "version": "2.0.2",
602 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
603 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
604 | "dependencies": {
605 | "function-bind": "^1.1.2"
606 | },
607 | "engines": {
608 | "node": ">= 0.4"
609 | }
610 | },
611 | "node_modules/http-errors": {
612 | "version": "2.0.0",
613 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
614 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
615 | "dependencies": {
616 | "depd": "2.0.0",
617 | "inherits": "2.0.4",
618 | "setprototypeof": "1.2.0",
619 | "statuses": "2.0.1",
620 | "toidentifier": "1.0.1"
621 | },
622 | "engines": {
623 | "node": ">= 0.8"
624 | }
625 | },
626 | "node_modules/iconv-lite": {
627 | "version": "0.4.24",
628 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
629 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
630 | "dependencies": {
631 | "safer-buffer": ">= 2.1.2 < 3"
632 | },
633 | "engines": {
634 | "node": ">=0.10.0"
635 | }
636 | },
637 | "node_modules/ignore-by-default": {
638 | "version": "1.0.1",
639 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
640 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
641 | "dev": true
642 | },
643 | "node_modules/inherits": {
644 | "version": "2.0.4",
645 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
646 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
647 | },
648 | "node_modules/ipaddr.js": {
649 | "version": "1.9.1",
650 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
651 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
652 | "engines": {
653 | "node": ">= 0.10"
654 | }
655 | },
656 | "node_modules/is-binary-path": {
657 | "version": "2.1.0",
658 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
659 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
660 | "dev": true,
661 | "dependencies": {
662 | "binary-extensions": "^2.0.0"
663 | },
664 | "engines": {
665 | "node": ">=8"
666 | }
667 | },
668 | "node_modules/is-extglob": {
669 | "version": "2.1.1",
670 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
671 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
672 | "dev": true,
673 | "engines": {
674 | "node": ">=0.10.0"
675 | }
676 | },
677 | "node_modules/is-glob": {
678 | "version": "4.0.3",
679 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
680 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
681 | "dev": true,
682 | "dependencies": {
683 | "is-extglob": "^2.1.1"
684 | },
685 | "engines": {
686 | "node": ">=0.10.0"
687 | }
688 | },
689 | "node_modules/is-number": {
690 | "version": "7.0.0",
691 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
692 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
693 | "dev": true,
694 | "engines": {
695 | "node": ">=0.12.0"
696 | }
697 | },
698 | "node_modules/isarray": {
699 | "version": "0.0.1",
700 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
701 | "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
702 | },
703 | "node_modules/kafkajs": {
704 | "version": "2.2.4",
705 | "resolved": "https://registry.npmjs.org/kafkajs/-/kafkajs-2.2.4.tgz",
706 | "integrity": "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA==",
707 | "engines": {
708 | "node": ">=14.0.0"
709 | }
710 | },
711 | "node_modules/media-typer": {
712 | "version": "0.3.0",
713 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
714 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
715 | "engines": {
716 | "node": ">= 0.6"
717 | }
718 | },
719 | "node_modules/merge-descriptors": {
720 | "version": "1.0.1",
721 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
722 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
723 | },
724 | "node_modules/methods": {
725 | "version": "1.1.2",
726 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
727 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
728 | "engines": {
729 | "node": ">= 0.6"
730 | }
731 | },
732 | "node_modules/mime": {
733 | "version": "1.6.0",
734 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
735 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
736 | "bin": {
737 | "mime": "cli.js"
738 | },
739 | "engines": {
740 | "node": ">=4"
741 | }
742 | },
743 | "node_modules/mime-db": {
744 | "version": "1.52.0",
745 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
746 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
747 | "engines": {
748 | "node": ">= 0.6"
749 | }
750 | },
751 | "node_modules/mime-types": {
752 | "version": "2.1.35",
753 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
754 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
755 | "dependencies": {
756 | "mime-db": "1.52.0"
757 | },
758 | "engines": {
759 | "node": ">= 0.6"
760 | }
761 | },
762 | "node_modules/minimatch": {
763 | "version": "3.1.2",
764 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
765 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
766 | "dev": true,
767 | "dependencies": {
768 | "brace-expansion": "^1.1.7"
769 | },
770 | "engines": {
771 | "node": "*"
772 | }
773 | },
774 | "node_modules/ms": {
775 | "version": "2.1.2",
776 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
777 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
778 | },
779 | "node_modules/negotiator": {
780 | "version": "0.6.3",
781 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
782 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
783 | "engines": {
784 | "node": ">= 0.6"
785 | }
786 | },
787 | "node_modules/nodemon": {
788 | "version": "3.1.3",
789 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz",
790 | "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==",
791 | "dev": true,
792 | "dependencies": {
793 | "chokidar": "^3.5.2",
794 | "debug": "^4",
795 | "ignore-by-default": "^1.0.1",
796 | "minimatch": "^3.1.2",
797 | "pstree.remy": "^1.1.8",
798 | "semver": "^7.5.3",
799 | "simple-update-notifier": "^2.0.0",
800 | "supports-color": "^5.5.0",
801 | "touch": "^3.1.0",
802 | "undefsafe": "^2.0.5"
803 | },
804 | "bin": {
805 | "nodemon": "bin/nodemon.js"
806 | },
807 | "engines": {
808 | "node": ">=10"
809 | },
810 | "funding": {
811 | "type": "opencollective",
812 | "url": "https://opencollective.com/nodemon"
813 | }
814 | },
815 | "node_modules/normalize-path": {
816 | "version": "3.0.0",
817 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
818 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
819 | "dev": true,
820 | "engines": {
821 | "node": ">=0.10.0"
822 | }
823 | },
824 | "node_modules/object-inspect": {
825 | "version": "1.13.1",
826 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
827 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
828 | "funding": {
829 | "url": "https://github.com/sponsors/ljharb"
830 | }
831 | },
832 | "node_modules/on-finished": {
833 | "version": "2.4.1",
834 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
835 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
836 | "dependencies": {
837 | "ee-first": "1.1.1"
838 | },
839 | "engines": {
840 | "node": ">= 0.8"
841 | }
842 | },
843 | "node_modules/parseurl": {
844 | "version": "1.3.3",
845 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
846 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
847 | "engines": {
848 | "node": ">= 0.8"
849 | }
850 | },
851 | "node_modules/path-to-regexp": {
852 | "version": "0.1.7",
853 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
854 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
855 | },
856 | "node_modules/picomatch": {
857 | "version": "2.3.1",
858 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
859 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
860 | "dev": true,
861 | "engines": {
862 | "node": ">=8.6"
863 | },
864 | "funding": {
865 | "url": "https://github.com/sponsors/jonschlinkert"
866 | }
867 | },
868 | "node_modules/proxy-addr": {
869 | "version": "2.0.7",
870 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
871 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
872 | "dependencies": {
873 | "forwarded": "0.2.0",
874 | "ipaddr.js": "1.9.1"
875 | },
876 | "engines": {
877 | "node": ">= 0.10"
878 | }
879 | },
880 | "node_modules/pstree.remy": {
881 | "version": "1.1.8",
882 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
883 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
884 | "dev": true
885 | },
886 | "node_modules/qs": {
887 | "version": "6.11.0",
888 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
889 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
890 | "dependencies": {
891 | "side-channel": "^1.0.4"
892 | },
893 | "engines": {
894 | "node": ">=0.6"
895 | },
896 | "funding": {
897 | "url": "https://github.com/sponsors/ljharb"
898 | }
899 | },
900 | "node_modules/querystringify": {
901 | "version": "2.2.0",
902 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
903 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
904 | },
905 | "node_modules/range-parser": {
906 | "version": "1.2.1",
907 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
908 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
909 | "engines": {
910 | "node": ">= 0.6"
911 | }
912 | },
913 | "node_modules/raw-body": {
914 | "version": "2.5.2",
915 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
916 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
917 | "dependencies": {
918 | "bytes": "3.1.2",
919 | "http-errors": "2.0.0",
920 | "iconv-lite": "0.4.24",
921 | "unpipe": "1.0.0"
922 | },
923 | "engines": {
924 | "node": ">= 0.8"
925 | }
926 | },
927 | "node_modules/readable-stream": {
928 | "version": "1.1.14",
929 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
930 | "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
931 | "dependencies": {
932 | "core-util-is": "~1.0.0",
933 | "inherits": "~2.0.1",
934 | "isarray": "0.0.1",
935 | "string_decoder": "~0.10.x"
936 | }
937 | },
938 | "node_modules/readdirp": {
939 | "version": "3.6.0",
940 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
941 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
942 | "dev": true,
943 | "dependencies": {
944 | "picomatch": "^2.2.1"
945 | },
946 | "engines": {
947 | "node": ">=8.10.0"
948 | }
949 | },
950 | "node_modules/requires-port": {
951 | "version": "1.0.0",
952 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
953 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
954 | },
955 | "node_modules/safe-buffer": {
956 | "version": "5.1.2",
957 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
958 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
959 | },
960 | "node_modules/safer-buffer": {
961 | "version": "2.1.2",
962 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
963 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
964 | },
965 | "node_modules/semver": {
966 | "version": "7.6.2",
967 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
968 | "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
969 | "dev": true,
970 | "bin": {
971 | "semver": "bin/semver.js"
972 | },
973 | "engines": {
974 | "node": ">=10"
975 | }
976 | },
977 | "node_modules/send": {
978 | "version": "0.18.0",
979 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
980 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
981 | "dependencies": {
982 | "debug": "2.6.9",
983 | "depd": "2.0.0",
984 | "destroy": "1.2.0",
985 | "encodeurl": "~1.0.2",
986 | "escape-html": "~1.0.3",
987 | "etag": "~1.8.1",
988 | "fresh": "0.5.2",
989 | "http-errors": "2.0.0",
990 | "mime": "1.6.0",
991 | "ms": "2.1.3",
992 | "on-finished": "2.4.1",
993 | "range-parser": "~1.2.1",
994 | "statuses": "2.0.1"
995 | },
996 | "engines": {
997 | "node": ">= 0.8.0"
998 | }
999 | },
1000 | "node_modules/send/node_modules/debug": {
1001 | "version": "2.6.9",
1002 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
1003 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
1004 | "dependencies": {
1005 | "ms": "2.0.0"
1006 | }
1007 | },
1008 | "node_modules/send/node_modules/debug/node_modules/ms": {
1009 | "version": "2.0.0",
1010 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1011 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
1012 | },
1013 | "node_modules/send/node_modules/ms": {
1014 | "version": "2.1.3",
1015 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1016 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1017 | },
1018 | "node_modules/serve-static": {
1019 | "version": "1.15.0",
1020 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1021 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1022 | "dependencies": {
1023 | "encodeurl": "~1.0.2",
1024 | "escape-html": "~1.0.3",
1025 | "parseurl": "~1.3.3",
1026 | "send": "0.18.0"
1027 | },
1028 | "engines": {
1029 | "node": ">= 0.8.0"
1030 | }
1031 | },
1032 | "node_modules/set-function-length": {
1033 | "version": "1.2.2",
1034 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
1035 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
1036 | "dependencies": {
1037 | "define-data-property": "^1.1.4",
1038 | "es-errors": "^1.3.0",
1039 | "function-bind": "^1.1.2",
1040 | "get-intrinsic": "^1.2.4",
1041 | "gopd": "^1.0.1",
1042 | "has-property-descriptors": "^1.0.2"
1043 | },
1044 | "engines": {
1045 | "node": ">= 0.4"
1046 | }
1047 | },
1048 | "node_modules/setprototypeof": {
1049 | "version": "1.2.0",
1050 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1051 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1052 | },
1053 | "node_modules/side-channel": {
1054 | "version": "1.0.6",
1055 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
1056 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
1057 | "dependencies": {
1058 | "call-bind": "^1.0.7",
1059 | "es-errors": "^1.3.0",
1060 | "get-intrinsic": "^1.2.4",
1061 | "object-inspect": "^1.13.1"
1062 | },
1063 | "engines": {
1064 | "node": ">= 0.4"
1065 | },
1066 | "funding": {
1067 | "url": "https://github.com/sponsors/ljharb"
1068 | }
1069 | },
1070 | "node_modules/simple-update-notifier": {
1071 | "version": "2.0.0",
1072 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
1073 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
1074 | "dev": true,
1075 | "dependencies": {
1076 | "semver": "^7.5.3"
1077 | },
1078 | "engines": {
1079 | "node": ">=10"
1080 | }
1081 | },
1082 | "node_modules/statuses": {
1083 | "version": "2.0.1",
1084 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1085 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1086 | "engines": {
1087 | "node": ">= 0.8"
1088 | }
1089 | },
1090 | "node_modules/string_decoder": {
1091 | "version": "0.10.31",
1092 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
1093 | "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
1094 | },
1095 | "node_modules/supports-color": {
1096 | "version": "5.5.0",
1097 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1098 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1099 | "dev": true,
1100 | "dependencies": {
1101 | "has-flag": "^3.0.0"
1102 | },
1103 | "engines": {
1104 | "node": ">=4"
1105 | }
1106 | },
1107 | "node_modules/to-regex-range": {
1108 | "version": "5.0.1",
1109 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1110 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1111 | "dev": true,
1112 | "dependencies": {
1113 | "is-number": "^7.0.0"
1114 | },
1115 | "engines": {
1116 | "node": ">=8.0"
1117 | }
1118 | },
1119 | "node_modules/toidentifier": {
1120 | "version": "1.0.1",
1121 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1122 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1123 | "engines": {
1124 | "node": ">=0.6"
1125 | }
1126 | },
1127 | "node_modules/touch": {
1128 | "version": "3.1.1",
1129 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
1130 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
1131 | "dev": true,
1132 | "bin": {
1133 | "nodetouch": "bin/nodetouch.js"
1134 | }
1135 | },
1136 | "node_modules/type-is": {
1137 | "version": "1.6.18",
1138 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1139 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1140 | "dependencies": {
1141 | "media-typer": "0.3.0",
1142 | "mime-types": "~2.1.24"
1143 | },
1144 | "engines": {
1145 | "node": ">= 0.6"
1146 | }
1147 | },
1148 | "node_modules/undefsafe": {
1149 | "version": "2.0.5",
1150 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1151 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1152 | "dev": true
1153 | },
1154 | "node_modules/unpipe": {
1155 | "version": "1.0.0",
1156 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1157 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1158 | "engines": {
1159 | "node": ">= 0.8"
1160 | }
1161 | },
1162 | "node_modules/url-parse": {
1163 | "version": "1.5.10",
1164 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
1165 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
1166 | "dependencies": {
1167 | "querystringify": "^2.1.1",
1168 | "requires-port": "^1.0.0"
1169 | }
1170 | },
1171 | "node_modules/utils-merge": {
1172 | "version": "1.0.1",
1173 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1174 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1175 | "engines": {
1176 | "node": ">= 0.4.0"
1177 | }
1178 | },
1179 | "node_modules/vary": {
1180 | "version": "1.1.2",
1181 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1182 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1183 | "engines": {
1184 | "node": ">= 0.8"
1185 | }
1186 | }
1187 | }
1188 | }
1189 |
--------------------------------------------------------------------------------
/buy-api/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "buy-api",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "buy-api",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "amqplib": "^0.10.4",
13 | "axios": "^1.7.2",
14 | "cors": "^2.8.5",
15 | "express": "^4.19.2",
16 | "uuid": "^9.0.1"
17 | },
18 | "devDependencies": {
19 | "nodemon": "^3.1.3"
20 | }
21 | },
22 | "node_modules/@acuminous/bitsyntax": {
23 | "version": "0.1.2",
24 | "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz",
25 | "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==",
26 | "dependencies": {
27 | "buffer-more-ints": "~1.0.0",
28 | "debug": "^4.3.4",
29 | "safe-buffer": "~5.1.2"
30 | },
31 | "engines": {
32 | "node": ">=0.8"
33 | }
34 | },
35 | "node_modules/@acuminous/bitsyntax/node_modules/debug": {
36 | "version": "4.3.5",
37 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
38 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
39 | "dependencies": {
40 | "ms": "2.1.2"
41 | },
42 | "engines": {
43 | "node": ">=6.0"
44 | },
45 | "peerDependenciesMeta": {
46 | "supports-color": {
47 | "optional": true
48 | }
49 | }
50 | },
51 | "node_modules/@acuminous/bitsyntax/node_modules/ms": {
52 | "version": "2.1.2",
53 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
54 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
55 | },
56 | "node_modules/@acuminous/bitsyntax/node_modules/safe-buffer": {
57 | "version": "5.1.2",
58 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
59 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
60 | },
61 | "node_modules/accepts": {
62 | "version": "1.3.8",
63 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
64 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
65 | "dependencies": {
66 | "mime-types": "~2.1.34",
67 | "negotiator": "0.6.3"
68 | },
69 | "engines": {
70 | "node": ">= 0.6"
71 | }
72 | },
73 | "node_modules/amqplib": {
74 | "version": "0.10.4",
75 | "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.4.tgz",
76 | "integrity": "sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==",
77 | "dependencies": {
78 | "@acuminous/bitsyntax": "^0.1.2",
79 | "buffer-more-ints": "~1.0.0",
80 | "readable-stream": "1.x >=1.1.9",
81 | "url-parse": "~1.5.10"
82 | },
83 | "engines": {
84 | "node": ">=10"
85 | }
86 | },
87 | "node_modules/anymatch": {
88 | "version": "3.1.3",
89 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
90 | "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
91 | "dev": true,
92 | "dependencies": {
93 | "normalize-path": "^3.0.0",
94 | "picomatch": "^2.0.4"
95 | },
96 | "engines": {
97 | "node": ">= 8"
98 | }
99 | },
100 | "node_modules/array-flatten": {
101 | "version": "1.1.1",
102 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
103 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
104 | },
105 | "node_modules/asynckit": {
106 | "version": "0.4.0",
107 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
108 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
109 | },
110 | "node_modules/axios": {
111 | "version": "1.7.2",
112 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz",
113 | "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==",
114 | "dependencies": {
115 | "follow-redirects": "^1.15.6",
116 | "form-data": "^4.0.0",
117 | "proxy-from-env": "^1.1.0"
118 | }
119 | },
120 | "node_modules/balanced-match": {
121 | "version": "1.0.2",
122 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
123 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
124 | "dev": true
125 | },
126 | "node_modules/binary-extensions": {
127 | "version": "2.3.0",
128 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
129 | "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
130 | "dev": true,
131 | "engines": {
132 | "node": ">=8"
133 | },
134 | "funding": {
135 | "url": "https://github.com/sponsors/sindresorhus"
136 | }
137 | },
138 | "node_modules/body-parser": {
139 | "version": "1.20.2",
140 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
141 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
142 | "dependencies": {
143 | "bytes": "3.1.2",
144 | "content-type": "~1.0.5",
145 | "debug": "2.6.9",
146 | "depd": "2.0.0",
147 | "destroy": "1.2.0",
148 | "http-errors": "2.0.0",
149 | "iconv-lite": "0.4.24",
150 | "on-finished": "2.4.1",
151 | "qs": "6.11.0",
152 | "raw-body": "2.5.2",
153 | "type-is": "~1.6.18",
154 | "unpipe": "1.0.0"
155 | },
156 | "engines": {
157 | "node": ">= 0.8",
158 | "npm": "1.2.8000 || >= 1.4.16"
159 | }
160 | },
161 | "node_modules/brace-expansion": {
162 | "version": "1.1.11",
163 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
164 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
165 | "dev": true,
166 | "dependencies": {
167 | "balanced-match": "^1.0.0",
168 | "concat-map": "0.0.1"
169 | }
170 | },
171 | "node_modules/braces": {
172 | "version": "3.0.3",
173 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
174 | "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
175 | "dev": true,
176 | "dependencies": {
177 | "fill-range": "^7.1.1"
178 | },
179 | "engines": {
180 | "node": ">=8"
181 | }
182 | },
183 | "node_modules/buffer-more-ints": {
184 | "version": "1.0.0",
185 | "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
186 | "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
187 | },
188 | "node_modules/bytes": {
189 | "version": "3.1.2",
190 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
191 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
192 | "engines": {
193 | "node": ">= 0.8"
194 | }
195 | },
196 | "node_modules/call-bind": {
197 | "version": "1.0.7",
198 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
199 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
200 | "dependencies": {
201 | "es-define-property": "^1.0.0",
202 | "es-errors": "^1.3.0",
203 | "function-bind": "^1.1.2",
204 | "get-intrinsic": "^1.2.4",
205 | "set-function-length": "^1.2.1"
206 | },
207 | "engines": {
208 | "node": ">= 0.4"
209 | },
210 | "funding": {
211 | "url": "https://github.com/sponsors/ljharb"
212 | }
213 | },
214 | "node_modules/chokidar": {
215 | "version": "3.6.0",
216 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
217 | "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
218 | "dev": true,
219 | "dependencies": {
220 | "anymatch": "~3.1.2",
221 | "braces": "~3.0.2",
222 | "glob-parent": "~5.1.2",
223 | "is-binary-path": "~2.1.0",
224 | "is-glob": "~4.0.1",
225 | "normalize-path": "~3.0.0",
226 | "readdirp": "~3.6.0"
227 | },
228 | "engines": {
229 | "node": ">= 8.10.0"
230 | },
231 | "funding": {
232 | "url": "https://paulmillr.com/funding/"
233 | },
234 | "optionalDependencies": {
235 | "fsevents": "~2.3.2"
236 | }
237 | },
238 | "node_modules/combined-stream": {
239 | "version": "1.0.8",
240 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
241 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
242 | "dependencies": {
243 | "delayed-stream": "~1.0.0"
244 | },
245 | "engines": {
246 | "node": ">= 0.8"
247 | }
248 | },
249 | "node_modules/concat-map": {
250 | "version": "0.0.1",
251 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
252 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
253 | "dev": true
254 | },
255 | "node_modules/content-disposition": {
256 | "version": "0.5.4",
257 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
258 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
259 | "dependencies": {
260 | "safe-buffer": "5.2.1"
261 | },
262 | "engines": {
263 | "node": ">= 0.6"
264 | }
265 | },
266 | "node_modules/content-type": {
267 | "version": "1.0.5",
268 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
269 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
270 | "engines": {
271 | "node": ">= 0.6"
272 | }
273 | },
274 | "node_modules/cookie": {
275 | "version": "0.6.0",
276 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
277 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
278 | "engines": {
279 | "node": ">= 0.6"
280 | }
281 | },
282 | "node_modules/cookie-signature": {
283 | "version": "1.0.6",
284 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
285 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
286 | },
287 | "node_modules/core-util-is": {
288 | "version": "1.0.3",
289 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
290 | "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
291 | },
292 | "node_modules/cors": {
293 | "version": "2.8.5",
294 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
295 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
296 | "dependencies": {
297 | "object-assign": "^4",
298 | "vary": "^1"
299 | },
300 | "engines": {
301 | "node": ">= 0.10"
302 | }
303 | },
304 | "node_modules/debug": {
305 | "version": "2.6.9",
306 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
307 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
308 | "dependencies": {
309 | "ms": "2.0.0"
310 | }
311 | },
312 | "node_modules/define-data-property": {
313 | "version": "1.1.4",
314 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
315 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
316 | "dependencies": {
317 | "es-define-property": "^1.0.0",
318 | "es-errors": "^1.3.0",
319 | "gopd": "^1.0.1"
320 | },
321 | "engines": {
322 | "node": ">= 0.4"
323 | },
324 | "funding": {
325 | "url": "https://github.com/sponsors/ljharb"
326 | }
327 | },
328 | "node_modules/delayed-stream": {
329 | "version": "1.0.0",
330 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
331 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
332 | "engines": {
333 | "node": ">=0.4.0"
334 | }
335 | },
336 | "node_modules/depd": {
337 | "version": "2.0.0",
338 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
339 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
340 | "engines": {
341 | "node": ">= 0.8"
342 | }
343 | },
344 | "node_modules/destroy": {
345 | "version": "1.2.0",
346 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
347 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
348 | "engines": {
349 | "node": ">= 0.8",
350 | "npm": "1.2.8000 || >= 1.4.16"
351 | }
352 | },
353 | "node_modules/ee-first": {
354 | "version": "1.1.1",
355 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
356 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
357 | },
358 | "node_modules/encodeurl": {
359 | "version": "1.0.2",
360 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
361 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
362 | "engines": {
363 | "node": ">= 0.8"
364 | }
365 | },
366 | "node_modules/es-define-property": {
367 | "version": "1.0.0",
368 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
369 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
370 | "dependencies": {
371 | "get-intrinsic": "^1.2.4"
372 | },
373 | "engines": {
374 | "node": ">= 0.4"
375 | }
376 | },
377 | "node_modules/es-errors": {
378 | "version": "1.3.0",
379 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
380 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
381 | "engines": {
382 | "node": ">= 0.4"
383 | }
384 | },
385 | "node_modules/escape-html": {
386 | "version": "1.0.3",
387 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
388 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
389 | },
390 | "node_modules/etag": {
391 | "version": "1.8.1",
392 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
393 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
394 | "engines": {
395 | "node": ">= 0.6"
396 | }
397 | },
398 | "node_modules/express": {
399 | "version": "4.19.2",
400 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
401 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
402 | "dependencies": {
403 | "accepts": "~1.3.8",
404 | "array-flatten": "1.1.1",
405 | "body-parser": "1.20.2",
406 | "content-disposition": "0.5.4",
407 | "content-type": "~1.0.4",
408 | "cookie": "0.6.0",
409 | "cookie-signature": "1.0.6",
410 | "debug": "2.6.9",
411 | "depd": "2.0.0",
412 | "encodeurl": "~1.0.2",
413 | "escape-html": "~1.0.3",
414 | "etag": "~1.8.1",
415 | "finalhandler": "1.2.0",
416 | "fresh": "0.5.2",
417 | "http-errors": "2.0.0",
418 | "merge-descriptors": "1.0.1",
419 | "methods": "~1.1.2",
420 | "on-finished": "2.4.1",
421 | "parseurl": "~1.3.3",
422 | "path-to-regexp": "0.1.7",
423 | "proxy-addr": "~2.0.7",
424 | "qs": "6.11.0",
425 | "range-parser": "~1.2.1",
426 | "safe-buffer": "5.2.1",
427 | "send": "0.18.0",
428 | "serve-static": "1.15.0",
429 | "setprototypeof": "1.2.0",
430 | "statuses": "2.0.1",
431 | "type-is": "~1.6.18",
432 | "utils-merge": "1.0.1",
433 | "vary": "~1.1.2"
434 | },
435 | "engines": {
436 | "node": ">= 0.10.0"
437 | }
438 | },
439 | "node_modules/fill-range": {
440 | "version": "7.1.1",
441 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
442 | "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
443 | "dev": true,
444 | "dependencies": {
445 | "to-regex-range": "^5.0.1"
446 | },
447 | "engines": {
448 | "node": ">=8"
449 | }
450 | },
451 | "node_modules/finalhandler": {
452 | "version": "1.2.0",
453 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
454 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
455 | "dependencies": {
456 | "debug": "2.6.9",
457 | "encodeurl": "~1.0.2",
458 | "escape-html": "~1.0.3",
459 | "on-finished": "2.4.1",
460 | "parseurl": "~1.3.3",
461 | "statuses": "2.0.1",
462 | "unpipe": "~1.0.0"
463 | },
464 | "engines": {
465 | "node": ">= 0.8"
466 | }
467 | },
468 | "node_modules/follow-redirects": {
469 | "version": "1.15.6",
470 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
471 | "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
472 | "funding": [
473 | {
474 | "type": "individual",
475 | "url": "https://github.com/sponsors/RubenVerborgh"
476 | }
477 | ],
478 | "engines": {
479 | "node": ">=4.0"
480 | },
481 | "peerDependenciesMeta": {
482 | "debug": {
483 | "optional": true
484 | }
485 | }
486 | },
487 | "node_modules/form-data": {
488 | "version": "4.0.0",
489 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
490 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
491 | "dependencies": {
492 | "asynckit": "^0.4.0",
493 | "combined-stream": "^1.0.8",
494 | "mime-types": "^2.1.12"
495 | },
496 | "engines": {
497 | "node": ">= 6"
498 | }
499 | },
500 | "node_modules/forwarded": {
501 | "version": "0.2.0",
502 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
503 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
504 | "engines": {
505 | "node": ">= 0.6"
506 | }
507 | },
508 | "node_modules/fresh": {
509 | "version": "0.5.2",
510 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
511 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
512 | "engines": {
513 | "node": ">= 0.6"
514 | }
515 | },
516 | "node_modules/fsevents": {
517 | "version": "2.3.3",
518 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
519 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
520 | "dev": true,
521 | "hasInstallScript": true,
522 | "optional": true,
523 | "os": [
524 | "darwin"
525 | ],
526 | "engines": {
527 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
528 | }
529 | },
530 | "node_modules/function-bind": {
531 | "version": "1.1.2",
532 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
533 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
534 | "funding": {
535 | "url": "https://github.com/sponsors/ljharb"
536 | }
537 | },
538 | "node_modules/get-intrinsic": {
539 | "version": "1.2.4",
540 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
541 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
542 | "dependencies": {
543 | "es-errors": "^1.3.0",
544 | "function-bind": "^1.1.2",
545 | "has-proto": "^1.0.1",
546 | "has-symbols": "^1.0.3",
547 | "hasown": "^2.0.0"
548 | },
549 | "engines": {
550 | "node": ">= 0.4"
551 | },
552 | "funding": {
553 | "url": "https://github.com/sponsors/ljharb"
554 | }
555 | },
556 | "node_modules/glob-parent": {
557 | "version": "5.1.2",
558 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
559 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
560 | "dev": true,
561 | "dependencies": {
562 | "is-glob": "^4.0.1"
563 | },
564 | "engines": {
565 | "node": ">= 6"
566 | }
567 | },
568 | "node_modules/gopd": {
569 | "version": "1.0.1",
570 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
571 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
572 | "dependencies": {
573 | "get-intrinsic": "^1.1.3"
574 | },
575 | "funding": {
576 | "url": "https://github.com/sponsors/ljharb"
577 | }
578 | },
579 | "node_modules/has-flag": {
580 | "version": "3.0.0",
581 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
582 | "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
583 | "dev": true,
584 | "engines": {
585 | "node": ">=4"
586 | }
587 | },
588 | "node_modules/has-property-descriptors": {
589 | "version": "1.0.2",
590 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
591 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
592 | "dependencies": {
593 | "es-define-property": "^1.0.0"
594 | },
595 | "funding": {
596 | "url": "https://github.com/sponsors/ljharb"
597 | }
598 | },
599 | "node_modules/has-proto": {
600 | "version": "1.0.3",
601 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
602 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
603 | "engines": {
604 | "node": ">= 0.4"
605 | },
606 | "funding": {
607 | "url": "https://github.com/sponsors/ljharb"
608 | }
609 | },
610 | "node_modules/has-symbols": {
611 | "version": "1.0.3",
612 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
613 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
614 | "engines": {
615 | "node": ">= 0.4"
616 | },
617 | "funding": {
618 | "url": "https://github.com/sponsors/ljharb"
619 | }
620 | },
621 | "node_modules/hasown": {
622 | "version": "2.0.2",
623 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
624 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
625 | "dependencies": {
626 | "function-bind": "^1.1.2"
627 | },
628 | "engines": {
629 | "node": ">= 0.4"
630 | }
631 | },
632 | "node_modules/http-errors": {
633 | "version": "2.0.0",
634 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
635 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
636 | "dependencies": {
637 | "depd": "2.0.0",
638 | "inherits": "2.0.4",
639 | "setprototypeof": "1.2.0",
640 | "statuses": "2.0.1",
641 | "toidentifier": "1.0.1"
642 | },
643 | "engines": {
644 | "node": ">= 0.8"
645 | }
646 | },
647 | "node_modules/iconv-lite": {
648 | "version": "0.4.24",
649 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
650 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
651 | "dependencies": {
652 | "safer-buffer": ">= 2.1.2 < 3"
653 | },
654 | "engines": {
655 | "node": ">=0.10.0"
656 | }
657 | },
658 | "node_modules/ignore-by-default": {
659 | "version": "1.0.1",
660 | "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
661 | "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
662 | "dev": true
663 | },
664 | "node_modules/inherits": {
665 | "version": "2.0.4",
666 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
667 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
668 | },
669 | "node_modules/ipaddr.js": {
670 | "version": "1.9.1",
671 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
672 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
673 | "engines": {
674 | "node": ">= 0.10"
675 | }
676 | },
677 | "node_modules/is-binary-path": {
678 | "version": "2.1.0",
679 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
680 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
681 | "dev": true,
682 | "dependencies": {
683 | "binary-extensions": "^2.0.0"
684 | },
685 | "engines": {
686 | "node": ">=8"
687 | }
688 | },
689 | "node_modules/is-extglob": {
690 | "version": "2.1.1",
691 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
692 | "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
693 | "dev": true,
694 | "engines": {
695 | "node": ">=0.10.0"
696 | }
697 | },
698 | "node_modules/is-glob": {
699 | "version": "4.0.3",
700 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
701 | "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
702 | "dev": true,
703 | "dependencies": {
704 | "is-extglob": "^2.1.1"
705 | },
706 | "engines": {
707 | "node": ">=0.10.0"
708 | }
709 | },
710 | "node_modules/is-number": {
711 | "version": "7.0.0",
712 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
713 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
714 | "dev": true,
715 | "engines": {
716 | "node": ">=0.12.0"
717 | }
718 | },
719 | "node_modules/isarray": {
720 | "version": "0.0.1",
721 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
722 | "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="
723 | },
724 | "node_modules/media-typer": {
725 | "version": "0.3.0",
726 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
727 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
728 | "engines": {
729 | "node": ">= 0.6"
730 | }
731 | },
732 | "node_modules/merge-descriptors": {
733 | "version": "1.0.1",
734 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
735 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
736 | },
737 | "node_modules/methods": {
738 | "version": "1.1.2",
739 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
740 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
741 | "engines": {
742 | "node": ">= 0.6"
743 | }
744 | },
745 | "node_modules/mime": {
746 | "version": "1.6.0",
747 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
748 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
749 | "bin": {
750 | "mime": "cli.js"
751 | },
752 | "engines": {
753 | "node": ">=4"
754 | }
755 | },
756 | "node_modules/mime-db": {
757 | "version": "1.52.0",
758 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
759 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
760 | "engines": {
761 | "node": ">= 0.6"
762 | }
763 | },
764 | "node_modules/mime-types": {
765 | "version": "2.1.35",
766 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
767 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
768 | "dependencies": {
769 | "mime-db": "1.52.0"
770 | },
771 | "engines": {
772 | "node": ">= 0.6"
773 | }
774 | },
775 | "node_modules/minimatch": {
776 | "version": "3.1.2",
777 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
778 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
779 | "dev": true,
780 | "dependencies": {
781 | "brace-expansion": "^1.1.7"
782 | },
783 | "engines": {
784 | "node": "*"
785 | }
786 | },
787 | "node_modules/ms": {
788 | "version": "2.0.0",
789 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
790 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
791 | },
792 | "node_modules/negotiator": {
793 | "version": "0.6.3",
794 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
795 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
796 | "engines": {
797 | "node": ">= 0.6"
798 | }
799 | },
800 | "node_modules/nodemon": {
801 | "version": "3.1.3",
802 | "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz",
803 | "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==",
804 | "dev": true,
805 | "dependencies": {
806 | "chokidar": "^3.5.2",
807 | "debug": "^4",
808 | "ignore-by-default": "^1.0.1",
809 | "minimatch": "^3.1.2",
810 | "pstree.remy": "^1.1.8",
811 | "semver": "^7.5.3",
812 | "simple-update-notifier": "^2.0.0",
813 | "supports-color": "^5.5.0",
814 | "touch": "^3.1.0",
815 | "undefsafe": "^2.0.5"
816 | },
817 | "bin": {
818 | "nodemon": "bin/nodemon.js"
819 | },
820 | "engines": {
821 | "node": ">=10"
822 | },
823 | "funding": {
824 | "type": "opencollective",
825 | "url": "https://opencollective.com/nodemon"
826 | }
827 | },
828 | "node_modules/nodemon/node_modules/debug": {
829 | "version": "4.3.5",
830 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
831 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
832 | "dev": true,
833 | "dependencies": {
834 | "ms": "2.1.2"
835 | },
836 | "engines": {
837 | "node": ">=6.0"
838 | },
839 | "peerDependenciesMeta": {
840 | "supports-color": {
841 | "optional": true
842 | }
843 | }
844 | },
845 | "node_modules/nodemon/node_modules/ms": {
846 | "version": "2.1.2",
847 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
848 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
849 | "dev": true
850 | },
851 | "node_modules/normalize-path": {
852 | "version": "3.0.0",
853 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
854 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
855 | "dev": true,
856 | "engines": {
857 | "node": ">=0.10.0"
858 | }
859 | },
860 | "node_modules/object-assign": {
861 | "version": "4.1.1",
862 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
863 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
864 | "engines": {
865 | "node": ">=0.10.0"
866 | }
867 | },
868 | "node_modules/object-inspect": {
869 | "version": "1.13.1",
870 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
871 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
872 | "funding": {
873 | "url": "https://github.com/sponsors/ljharb"
874 | }
875 | },
876 | "node_modules/on-finished": {
877 | "version": "2.4.1",
878 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
879 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
880 | "dependencies": {
881 | "ee-first": "1.1.1"
882 | },
883 | "engines": {
884 | "node": ">= 0.8"
885 | }
886 | },
887 | "node_modules/parseurl": {
888 | "version": "1.3.3",
889 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
890 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
891 | "engines": {
892 | "node": ">= 0.8"
893 | }
894 | },
895 | "node_modules/path-to-regexp": {
896 | "version": "0.1.7",
897 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
898 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
899 | },
900 | "node_modules/picomatch": {
901 | "version": "2.3.1",
902 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
903 | "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
904 | "dev": true,
905 | "engines": {
906 | "node": ">=8.6"
907 | },
908 | "funding": {
909 | "url": "https://github.com/sponsors/jonschlinkert"
910 | }
911 | },
912 | "node_modules/proxy-addr": {
913 | "version": "2.0.7",
914 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
915 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
916 | "dependencies": {
917 | "forwarded": "0.2.0",
918 | "ipaddr.js": "1.9.1"
919 | },
920 | "engines": {
921 | "node": ">= 0.10"
922 | }
923 | },
924 | "node_modules/proxy-from-env": {
925 | "version": "1.1.0",
926 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
927 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
928 | },
929 | "node_modules/pstree.remy": {
930 | "version": "1.1.8",
931 | "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
932 | "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
933 | "dev": true
934 | },
935 | "node_modules/qs": {
936 | "version": "6.11.0",
937 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
938 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
939 | "dependencies": {
940 | "side-channel": "^1.0.4"
941 | },
942 | "engines": {
943 | "node": ">=0.6"
944 | },
945 | "funding": {
946 | "url": "https://github.com/sponsors/ljharb"
947 | }
948 | },
949 | "node_modules/querystringify": {
950 | "version": "2.2.0",
951 | "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
952 | "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
953 | },
954 | "node_modules/range-parser": {
955 | "version": "1.2.1",
956 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
957 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
958 | "engines": {
959 | "node": ">= 0.6"
960 | }
961 | },
962 | "node_modules/raw-body": {
963 | "version": "2.5.2",
964 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
965 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
966 | "dependencies": {
967 | "bytes": "3.1.2",
968 | "http-errors": "2.0.0",
969 | "iconv-lite": "0.4.24",
970 | "unpipe": "1.0.0"
971 | },
972 | "engines": {
973 | "node": ">= 0.8"
974 | }
975 | },
976 | "node_modules/readable-stream": {
977 | "version": "1.1.14",
978 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
979 | "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==",
980 | "dependencies": {
981 | "core-util-is": "~1.0.0",
982 | "inherits": "~2.0.1",
983 | "isarray": "0.0.1",
984 | "string_decoder": "~0.10.x"
985 | }
986 | },
987 | "node_modules/readdirp": {
988 | "version": "3.6.0",
989 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
990 | "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
991 | "dev": true,
992 | "dependencies": {
993 | "picomatch": "^2.2.1"
994 | },
995 | "engines": {
996 | "node": ">=8.10.0"
997 | }
998 | },
999 | "node_modules/requires-port": {
1000 | "version": "1.0.0",
1001 | "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
1002 | "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
1003 | },
1004 | "node_modules/safe-buffer": {
1005 | "version": "5.2.1",
1006 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1007 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1008 | "funding": [
1009 | {
1010 | "type": "github",
1011 | "url": "https://github.com/sponsors/feross"
1012 | },
1013 | {
1014 | "type": "patreon",
1015 | "url": "https://www.patreon.com/feross"
1016 | },
1017 | {
1018 | "type": "consulting",
1019 | "url": "https://feross.org/support"
1020 | }
1021 | ]
1022 | },
1023 | "node_modules/safer-buffer": {
1024 | "version": "2.1.2",
1025 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1026 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
1027 | },
1028 | "node_modules/semver": {
1029 | "version": "7.6.2",
1030 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
1031 | "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
1032 | "dev": true,
1033 | "bin": {
1034 | "semver": "bin/semver.js"
1035 | },
1036 | "engines": {
1037 | "node": ">=10"
1038 | }
1039 | },
1040 | "node_modules/send": {
1041 | "version": "0.18.0",
1042 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
1043 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
1044 | "dependencies": {
1045 | "debug": "2.6.9",
1046 | "depd": "2.0.0",
1047 | "destroy": "1.2.0",
1048 | "encodeurl": "~1.0.2",
1049 | "escape-html": "~1.0.3",
1050 | "etag": "~1.8.1",
1051 | "fresh": "0.5.2",
1052 | "http-errors": "2.0.0",
1053 | "mime": "1.6.0",
1054 | "ms": "2.1.3",
1055 | "on-finished": "2.4.1",
1056 | "range-parser": "~1.2.1",
1057 | "statuses": "2.0.1"
1058 | },
1059 | "engines": {
1060 | "node": ">= 0.8.0"
1061 | }
1062 | },
1063 | "node_modules/send/node_modules/ms": {
1064 | "version": "2.1.3",
1065 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1066 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
1067 | },
1068 | "node_modules/serve-static": {
1069 | "version": "1.15.0",
1070 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
1071 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
1072 | "dependencies": {
1073 | "encodeurl": "~1.0.2",
1074 | "escape-html": "~1.0.3",
1075 | "parseurl": "~1.3.3",
1076 | "send": "0.18.0"
1077 | },
1078 | "engines": {
1079 | "node": ">= 0.8.0"
1080 | }
1081 | },
1082 | "node_modules/set-function-length": {
1083 | "version": "1.2.2",
1084 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
1085 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
1086 | "dependencies": {
1087 | "define-data-property": "^1.1.4",
1088 | "es-errors": "^1.3.0",
1089 | "function-bind": "^1.1.2",
1090 | "get-intrinsic": "^1.2.4",
1091 | "gopd": "^1.0.1",
1092 | "has-property-descriptors": "^1.0.2"
1093 | },
1094 | "engines": {
1095 | "node": ">= 0.4"
1096 | }
1097 | },
1098 | "node_modules/setprototypeof": {
1099 | "version": "1.2.0",
1100 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1101 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
1102 | },
1103 | "node_modules/side-channel": {
1104 | "version": "1.0.6",
1105 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
1106 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
1107 | "dependencies": {
1108 | "call-bind": "^1.0.7",
1109 | "es-errors": "^1.3.0",
1110 | "get-intrinsic": "^1.2.4",
1111 | "object-inspect": "^1.13.1"
1112 | },
1113 | "engines": {
1114 | "node": ">= 0.4"
1115 | },
1116 | "funding": {
1117 | "url": "https://github.com/sponsors/ljharb"
1118 | }
1119 | },
1120 | "node_modules/simple-update-notifier": {
1121 | "version": "2.0.0",
1122 | "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
1123 | "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
1124 | "dev": true,
1125 | "dependencies": {
1126 | "semver": "^7.5.3"
1127 | },
1128 | "engines": {
1129 | "node": ">=10"
1130 | }
1131 | },
1132 | "node_modules/statuses": {
1133 | "version": "2.0.1",
1134 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1135 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1136 | "engines": {
1137 | "node": ">= 0.8"
1138 | }
1139 | },
1140 | "node_modules/string_decoder": {
1141 | "version": "0.10.31",
1142 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
1143 | "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="
1144 | },
1145 | "node_modules/supports-color": {
1146 | "version": "5.5.0",
1147 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
1148 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
1149 | "dev": true,
1150 | "dependencies": {
1151 | "has-flag": "^3.0.0"
1152 | },
1153 | "engines": {
1154 | "node": ">=4"
1155 | }
1156 | },
1157 | "node_modules/to-regex-range": {
1158 | "version": "5.0.1",
1159 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
1160 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
1161 | "dev": true,
1162 | "dependencies": {
1163 | "is-number": "^7.0.0"
1164 | },
1165 | "engines": {
1166 | "node": ">=8.0"
1167 | }
1168 | },
1169 | "node_modules/toidentifier": {
1170 | "version": "1.0.1",
1171 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1172 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1173 | "engines": {
1174 | "node": ">=0.6"
1175 | }
1176 | },
1177 | "node_modules/touch": {
1178 | "version": "3.1.1",
1179 | "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
1180 | "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
1181 | "dev": true,
1182 | "bin": {
1183 | "nodetouch": "bin/nodetouch.js"
1184 | }
1185 | },
1186 | "node_modules/type-is": {
1187 | "version": "1.6.18",
1188 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1189 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1190 | "dependencies": {
1191 | "media-typer": "0.3.0",
1192 | "mime-types": "~2.1.24"
1193 | },
1194 | "engines": {
1195 | "node": ">= 0.6"
1196 | }
1197 | },
1198 | "node_modules/undefsafe": {
1199 | "version": "2.0.5",
1200 | "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
1201 | "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
1202 | "dev": true
1203 | },
1204 | "node_modules/unpipe": {
1205 | "version": "1.0.0",
1206 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1207 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1208 | "engines": {
1209 | "node": ">= 0.8"
1210 | }
1211 | },
1212 | "node_modules/url-parse": {
1213 | "version": "1.5.10",
1214 | "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
1215 | "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
1216 | "dependencies": {
1217 | "querystringify": "^2.1.1",
1218 | "requires-port": "^1.0.0"
1219 | }
1220 | },
1221 | "node_modules/utils-merge": {
1222 | "version": "1.0.1",
1223 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1224 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1225 | "engines": {
1226 | "node": ">= 0.4.0"
1227 | }
1228 | },
1229 | "node_modules/uuid": {
1230 | "version": "9.0.1",
1231 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
1232 | "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
1233 | "funding": [
1234 | "https://github.com/sponsors/broofa",
1235 | "https://github.com/sponsors/ctavan"
1236 | ],
1237 | "bin": {
1238 | "uuid": "dist/bin/uuid"
1239 | }
1240 | },
1241 | "node_modules/vary": {
1242 | "version": "1.1.2",
1243 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1244 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1245 | "engines": {
1246 | "node": ">= 0.8"
1247 | }
1248 | }
1249 | }
1250 | }
1251 |
--------------------------------------------------------------------------------