├── 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 | 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 | 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 | 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 | ![Architecture](./ecommerce-lab.jpg) 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 | --------------------------------------------------------------------------------