diff --git a/PROD_STAND/PG1/main.tf b/PROD_STAND/PG1/main.tf new file mode 100644 index 0000000..3b553b7 --- /dev/null +++ b/PROD_STAND/PG1/main.tf @@ -0,0 +1,25 @@ +terraform { + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "2.1.26" + } + } +} + +variable "api_token" { + type = string + sensitive = true + description = "Nubes API token" +} +# variable "s3_uid" { +# type = string +# sensitive = true +# description = "S3 Storage UUID" +# } + +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://deck-api.ngcloud.ru/api/v1/index.cfm" +} + diff --git a/PROD_STAND/PG1/resources.tf b/PROD_STAND/PG1/resources.tf new file mode 100644 index 0000000..f505129 --- /dev/null +++ b/PROD_STAND/PG1/resources.tf @@ -0,0 +1,141 @@ +resource "nubes_postgres" "db2" { + # Основной Postgres-кластер для демо. + resource_name = "pg-testing" + s3_uid = "6d6061cb-b0c1-44b9-8969-a70f08fe673c" + # s3_uid = "6d6061cb-b0c1-44b9-8969-a70f08fe673c" + resource_realm = "k8s-3.ext.nubes.ru" + resource_instances = 1 + resource_memory = 1024 + resource_c_p_u = 1000 + resource_disk = "1" + app_version = "17" + json_parameters = jsonencode({ + # Выключаем подробные логи подключений в демо. + log_connections = "off" + log_disconnections = "off" + }) + enable_pg_pooler_master = false + enable_pg_pooler_slave = false + allow_no_s_s_l = false + auto_scale = false + auto_scale_percentage = 10 + auto_scale_tech_window = 0 + auto_scale_quota_gb = "1" + need_external_address_master = false + + adopt_existing_on_create = true + +} + +# output "pg_vault_secrets" { +# value = nubes_postgres.db2.vault_secrets +# sensitive = true +# } +resource "nubes_lucee" "app1" { + # Lucee UI, который читает/пишет в Postgres. + resource_name = "lucy11" + resource_realm = nubes_postgres.db2.resource_realm + domain = "web07" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testlucee.git" + + json_env = jsonencode({ + # JDBC datasource для Lucee. + testds_class = "org.postgresql.Driver" + testds_bundleName = "org.postgresql.jdbc" + testds_bundleVersion = "42.6.0" + testds_connectionString = "jdbc:postgresql://${nubes_postgres.db2.state_out_flat["internalConnect.master"]}:5432/postgres?sslmode=require" + testds_username = nubes_postgres.db2.vault_secrets["adminUser"] + testds_password = nubes_postgres.db2.vault_secrets["adminPass"] + testds_connectionLimit = "7" + testds_liveTimeout = "15" + testds_validate = "false" + }) + + resource_c_p_u = 300 + resource_memory = 512 + resource_instances = 1 + app_version = "5.4" + + depends_on = [nubes_postgres.db2] + + adopt_existing_on_create = true + +} + +resource "nubes_s3bucket" "baba_bucket" { + resource_name = "baba-buck-test2" + s3_user_uid = "6d6061cb-b0c1-44b9-8969-a70f08fe673c" + bucket_name = "baba0" +# adopt_existing_on_create=true +} + + + +resource "nubes_nodejs" "app3" { + # NodeJS демо, работающий с тем же Postgres. + resource_name = "node_0111" + resource_realm = nubes_postgres.db2.resource_realm + domain = "node07" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testnode.git" + health_path = "/healthz" + app_version = "23" + + json_env = jsonencode({ + # Переменные подключения к Postgres. + PGHOST = nubes_postgres.db2.state_out_flat["internalConnect.master"] + PGPORT = "5432" + PGUSER = nubes_postgres.db2.vault_secrets["adminUser"] + PGPASSWORD = nubes_postgres.db2.vault_secrets["adminPass"] + PGSSLMODE = "require" + DATABASE_URL = format( + "postgresql://%s:%s@%s:5432/postgres", + nubes_postgres.db2.vault_secrets["adminUser"], + nubes_postgres.db2.vault_secrets["adminPass"], + nubes_postgres.db2.state_out_flat["internalConnect.master"] + ) + }) + + resource_c_p_u = 300 + resource_memory = 256 + resource_instances = 1 + + adopt_existing_on_create = true + # suspend_on_destroy = true + + depends_on = [nubes_postgres.db2] +} + +# resource "nubes_nodejs" "app4" { +# # NodeJS демо, работающий с тем же Postgres. +# resource_name = "node_0113" +# resource_realm = nubes_postgres.db2.resource_realm +# domain = "node07" +# git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testnode.git" +# health_path = "/healthz" +# app_version = "23" + +# json_env = jsonencode({ +# # Переменные подключения к Postgres. +# PGHOST = nubes_postgres.db2.state_out_flat["internalConnect.master"] +# PGPORT = "5432" +# PGUSER = nubes_postgres.db2.vault_secrets["adminUser"] +# PGPASSWORD = nubes_postgres.db2.vault_secrets["adminPass"] +# PGSSLMODE = "require" +# DATABASE_URL = format( +# "postgresql://%s:%s@%s:5432/postgres", +# nubes_postgres.db2.vault_secrets["adminUser"], +# nubes_postgres.db2.vault_secrets["adminPass"], +# nubes_postgres.db2.state_out_flat["internalConnect.master"] +# ) +# }) + +# resource_c_p_u = 300 +# resource_memory = 256 +# resource_instances = 1 + +# adopt_existing_on_create = true +# # suspend_on_destroy = true + +# depends_on = [nubes_postgres.db2] +# } + diff --git a/PROD_STAND/PG1/vm.tf b/PROD_STAND/PG1/vm.tf new file mode 100644 index 0000000..a34ea65 --- /dev/null +++ b/PROD_STAND/PG1/vm.tf @@ -0,0 +1,15 @@ +# resource "nubes_vc_vm_v3" "vmnginx01" { +# resource_name = "vm-nginx-auto" +# vapp_uid = "464eb4a0-62dc-42cd-af2b-4305db3c8d1b" +# vm_name = "vmnginx01" +# vm_cpu = 1 +# vm_ram = 1 +# vm_disk = 10 +# image_vm = "Ubuntu_22-20G" +# user_login = "root" +# user_public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDUd0DCo52wbKDRR6skYmwx6ffLkFewpXu/YSwtalNlVIhQaOCaEJmY5SerGVqgvXQakckptt3bTRGFPKThxr3LsOk9iftvNKQysr7HMSHWLk3nQvix8DaBIla2Ej+zGV/BfIkFA/85R/wwOC6wTLxOcByXKeJCuIx05NUXh0/5nRc3nowpwyKusUm9VBvMyLdZ4MER8bFq+QhdTaEf1sR3PWNQeDA6Oo4qn/HEU2AzqDI1zEPY8ZT9O6Uc30KdbIaEzAmKpw7F0ai0pmlqbzeDVGx4M4FewQei354d9zvu3CI2KryWQ88jm4feuMcl7wGrhD+kS2F+AUiHCJaPczeh" +# ip_space_name = "no-needed" +# access_ip_list = jsonencode([]) +# access_port_list = jsonencode(["22", "80", "10050"]) +# adopt_existing_on_create = true +# } diff --git a/PROD_STAND/POSTGRES/flask_disabled.tf b/PROD_STAND/POSTGRES/flask_disabled.tf new file mode 100644 index 0000000..9d61a7a --- /dev/null +++ b/PROD_STAND/POSTGRES/flask_disabled.tf @@ -0,0 +1,28 @@ +/* +resource "nubes_flask" "app4" { + resource_name = "flask_0" + resource_realm = nubes_postgres.db2.resource_realm + domain = "flask" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testflask.git" + + json_env = jsonencode({ + PGHOST = nubes_postgres.db2.state_out_flat["internalConnect.master"] + PGPORT = "5432" + PGUSER = nubes_postgres.db2.vault_secrets["adminUser"] + PGPASSWORD = nubes_postgres.db2.vault_secrets["adminPass"] + PGSSLMODE = "require" + DATABASE_URL = format( + "postgresql://%s:%s@%s:5432/postgres", + nubes_postgres.db2.vault_secrets["adminUser"], + nubes_postgres.db2.vault_secrets["adminPass"], + nubes_postgres.db2.state_out_flat["internalConnect.master"] + ) + }) + + resource_c_p_u = 300 + resource_memory = 256 + resource_instances = 1 + + depends_on = [nubes_postgres.db2] +} +*/ diff --git a/PROD_STAND/POSTGRES/main.tf b/PROD_STAND/POSTGRES/main.tf new file mode 100644 index 0000000..54bb050 --- /dev/null +++ b/PROD_STAND/POSTGRES/main.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "2.1.12" + } + } +} + +variable "api_token" { + type = string + sensitive = true + description = "Nubes API token" +} + +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://deck-api.ngcloud.ru/api/v1/index.cfm" +} + diff --git a/PROD_STAND/POSTGRES/resources.tf b/PROD_STAND/POSTGRES/resources.tf new file mode 100644 index 0000000..4d68230 --- /dev/null +++ b/PROD_STAND/POSTGRES/resources.tf @@ -0,0 +1,90 @@ +resource "nubes_postgres" "db2" { + # Основной Postgres-кластер для демо. + resource_name = "pg-tst0" + s3_uid = "6d6061cb-b0c1-44b9-8969-a70f08fe673c" + # s3_uid = "s3-111805" + resource_realm = "k8s-3.ext.nubes.ru" + resource_instances = 1 + resource_memory = 512 + resource_c_p_u = 500 + resource_disk = "1" + app_version = "17" + json_parameters = jsonencode({ + # Выключаем подробные логи подключений в демо. + log_connections = "off" + log_disconnections = "off" + }) + enable_pg_pooler_master = false + enable_pg_pooler_slave = false + allow_no_s_s_l = false + auto_scale = false + auto_scale_percentage = 10 + auto_scale_tech_window = 0 + auto_scale_quota_gb = "1" + need_external_address_master = false +} + +output "pg_vault_secrets" { + value = nubes_postgres.db2.vault_secrets + sensitive = true +} + +resource "nubes_lucee" "app1" { + # Lucee UI, который читает/пишет в Postgres. + resource_name = "lucy1" + resource_realm = nubes_postgres.db2.resource_realm + domain = "web03" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testlucee.git" + + json_env = jsonencode({ + # JDBC datasource для Lucee. + testds_class = "org.postgresql.Driver" + testds_bundleName = "org.postgresql.jdbc" + testds_bundleVersion = "42.6.0" + testds_connectionString = "jdbc:postgresql://${nubes_postgres.db2.state_out_flat["internalConnect.master"]}:5432/postgres" + testds_username = nubes_postgres.db2.vault_secrets["adminUser"] + testds_password = nubes_postgres.db2.vault_secrets["adminPass"] + testds_connectionLimit = "5" + testds_liveTimeout = "15" + testds_validate = "false" + }) + + resource_c_p_u = 300 + resource_memory = 512 + resource_instances = 1 + app_version = "5.4" + + depends_on = [nubes_postgres.db2] +} + +resource "nubes_nodejs" "app3" { + # NodeJS демо, работающий с тем же Postgres. + resource_name = "node_0" + resource_realm = nubes_postgres.db2.resource_realm + domain = "node" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testnode.git" + health_path = "/healthz" + app_version = "23" + + json_env = jsonencode({ + # Переменные подключения к Postgres. + PGHOST = nubes_postgres.db2.state_out_flat["internalConnect.master"] + PGPORT = "5432" + PGUSER = nubes_postgres.db2.vault_secrets["adminUser"] + PGPASSWORD = nubes_postgres.db2.vault_secrets["adminPass"] + PGSSLMODE = "require" + DATABASE_URL = format( + "postgresql://%s:%s@%s:5432/postgres", + nubes_postgres.db2.vault_secrets["adminUser"], + nubes_postgres.db2.vault_secrets["adminPass"], + nubes_postgres.db2.state_out_flat["internalConnect.master"] + ) + }) + + resource_c_p_u = 300 + resource_memory = 256 + resource_instances = 1 + + depends_on = [nubes_postgres.db2] +} + diff --git a/PROD_STAND/POSTGRES/testflask/.gitignore b/PROD_STAND/POSTGRES/testflask/.gitignore new file mode 100644 index 0000000..5cdf96c --- /dev/null +++ b/PROD_STAND/POSTGRES/testflask/.gitignore @@ -0,0 +1,3 @@ +site/templates/ +site/static/ +venv/ \ No newline at end of file diff --git a/PROD_STAND/POSTGRES/testflask/.pylintrc b/PROD_STAND/POSTGRES/testflask/.pylintrc new file mode 100644 index 0000000..489b748 --- /dev/null +++ b/PROD_STAND/POSTGRES/testflask/.pylintrc @@ -0,0 +1 @@ +disable=missing-function-docstring diff --git a/PROD_STAND/POSTGRES/testflask/Dockerfile b/PROD_STAND/POSTGRES/testflask/Dockerfile new file mode 100644 index 0000000..2a9fd7f --- /dev/null +++ b/PROD_STAND/POSTGRES/testflask/Dockerfile @@ -0,0 +1,14 @@ +# Базовый образ Python для Flask-приложения. +FROM python:3.9-slim + +WORKDIR /app + +COPY requirements.txt . + +RUN pip install --no-cache-dir -r requirements.txt + +COPY site /app/site + +EXPOSE 5000 + +CMD ["python", "site/app.py"] \ No newline at end of file diff --git a/PROD_STAND/POSTGRES/testflask/README.md b/PROD_STAND/POSTGRES/testflask/README.md new file mode 100644 index 0000000..f340985 --- /dev/null +++ b/PROD_STAND/POSTGRES/testflask/README.md @@ -0,0 +1,6 @@ +# Flask + Postgres Demo + +Простое CRUD-приложение на Flask, которое пишет и читает из таблицы `nubes_test_table`. +Если поле ввода пустое, вставляется запись "Это Flask сделал". + +Примечание: таблица создается автоматически при первом запросе. \ No newline at end of file diff --git a/PROD_STAND/POSTGRES/testflask/requirements.txt b/PROD_STAND/POSTGRES/testflask/requirements.txt new file mode 100644 index 0000000..b46927f --- /dev/null +++ b/PROD_STAND/POSTGRES/testflask/requirements.txt @@ -0,0 +1,3 @@ +Flask==2.0.1 +Werkzeug==2.3.7 +psycopg2-binary==2.9.9 \ No newline at end of file diff --git a/PROD_STAND/POSTGRES/testlucee/Application.cfc b/PROD_STAND/POSTGRES/testlucee/Application.cfc new file mode 100644 index 0000000..5cbfb85 --- /dev/null +++ b/PROD_STAND/POSTGRES/testlucee/Application.cfc @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO nubes_test_table (test_data) VALUES () + + + + + UPDATE nubes_test_table SET test_data = WHERE id = + + + + + DELETE FROM nubes_test_table WHERE id = + + + + + + + + + + + CREATE TABLE IF NOT EXISTS nubes_test_table (id SERIAL PRIMARY KEY, test_data TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP); + + + + + diff --git a/PROD_STAND/POSTGRES/testlucee/README.md b/PROD_STAND/POSTGRES/testlucee/README.md new file mode 100644 index 0000000..142e4d4 --- /dev/null +++ b/PROD_STAND/POSTGRES/testlucee/README.md @@ -0,0 +1,11 @@ +# Тестовое приложение by Terraform - Lucee & Postgres + +### Что здесь можно делать: + +* **Просмотр:** Список последних записей загружается автоматически при открытии страницы. +* **Добавление:** Введите текст в верхнее поле и нажмите кнопку «Добавить», чтобы сохранить данные в базу. +* **Редактирование:** Вы можете изменить текст любой записи прямо в таблице. Не забудьте нажать на кнопку с дискетой (💾), чтобы сохранить изменения. +* **Удаление:** Нажмите на иконку корзины (🗑), чтобы навсегда удалить запись из базы данных. + +Примечание: таблица создается автоматически при первом открытии страницы. + diff --git a/PROD_STAND/POSTGRES/testlucee/index.cfm b/PROD_STAND/POSTGRES/testlucee/index.cfm new file mode 100644 index 0000000..2221de8 --- /dev/null +++ b/PROD_STAND/POSTGRES/testlucee/index.cfm @@ -0,0 +1,2 @@ + + diff --git a/PROD_STAND/POSTGRES/testlucee/query.cfm b/PROD_STAND/POSTGRES/testlucee/query.cfm new file mode 100644 index 0000000..8943632 --- /dev/null +++ b/PROD_STAND/POSTGRES/testlucee/query.cfm @@ -0,0 +1,77 @@ + + + + + Nubes | Управление данными + + + + + +
+
+ +
Lucee + Postgres Demo
+
+
+
+ +
+
+ + + +
+ + SELECT * FROM nubes_test_table ORDER BY id DESC LIMIT 20 + + + + + + + + + + + +
IDСодержимоеДействия
#id# +
+ + +
+
+ +
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/PROD_STAND/POSTGRES/testlucee/tst.html b/PROD_STAND/POSTGRES/testlucee/tst.html new file mode 100644 index 0000000..0994e3b --- /dev/null +++ b/PROD_STAND/POSTGRES/testlucee/tst.html @@ -0,0 +1 @@ +again test hello \ No newline at end of file diff --git a/PROD_STAND/POSTGRES/testnode/.gitignore b/PROD_STAND/POSTGRES/testnode/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/PROD_STAND/POSTGRES/testnode/README.md b/PROD_STAND/POSTGRES/testnode/README.md new file mode 100644 index 0000000..a7601bd --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/README.md @@ -0,0 +1,6 @@ +# NodeJS + Postgres Demo + +Simple Node.js CRUD app that writes to `nubes_test_table` and renders a small HTML UI. +If the input is empty, it inserts "Node did it". + +Примечание: таблица создается автоматически, а дубликаты подряд отсекаются по таймауту. diff --git a/PROD_STAND/POSTGRES/testnode/package-lock.json b/PROD_STAND/POSTGRES/testnode/package-lock.json new file mode 100644 index 0000000..4f04357 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/package-lock.json @@ -0,0 +1,6808 @@ +{ + "name": "loopback4-example-github", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "loopback4-example-github", + "version": "0.0.1", + "dependencies": { + "@loopback/boot": "^3.4.1", + "@loopback/core": "^2.16.1", + "@loopback/repository": "^3.7.0", + "@loopback/rest": "^9.3.1", + "@loopback/rest-explorer": "^3.3.1", + "@loopback/service-proxy": "^3.2.1", + "loopback-connector-rest": "^3.7.0", + "pg": "^8.18.0", + "tslib": "^2.0.0" + }, + "devDependencies": { + "@loopback/build": "^6.4.1", + "@loopback/eslint-config": "^10.2.1", + "@loopback/testlab": "^3.4.1", + "@types/node": "^10.17.60", + "eslint": "^7.28.0", + "source-map-support": "^0.5.19", + "typescript": "~4.3.2" + }, + "engines": { + "node": ">=10.16" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.12.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-compilation-targets": "^7.14.5", + "@babel/helper-module-transforms": "^7.14.5", + "@babel/helpers": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/@babel/code-frame": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.0", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.14.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.14.5", + "@babel/helper-validator-option": "^7.14.5", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.0", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-get-function-arity": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-get-function-arity": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.14.5", + "@babel/helper-replace-supers": "^7.14.5", + "@babel/helper-simple-access": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/helper-validator-identifier": "^7.14.5", + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.14.5", + "@babel/helper-optimise-call-expression": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.14.5", + "@babel/traverse": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template/node_modules/@babel/code-frame": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.14.5", + "@babel/generator": "^7.14.5", + "@babel/helper-function-name": "^7.14.5", + "@babel/helper-hoist-variables": "^7.14.5", + "@babel/helper-split-export-declaration": "^7.14.5", + "@babel/parser": "^7.14.5", + "@babel/types": "^7.14.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@babel/code-frame": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/highlight": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.14.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "3.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@exodus/schemasafe": { + "version": "1.0.0-rc.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@hapi/hoek": { + "version": "9.2.0", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/shot": { + "version": "5.0.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "9.x.x", + "@hapi/validate": "1.x.x" + } + }, + "node_modules/@hapi/topo": { + "version": "5.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@hapi/validate": { + "version": "1.1.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@loopback/boot": { + "version": "3.4.1", + "license": "MIT", + "dependencies": { + "@loopback/model-api-builder": "^2.3.1", + "@loopback/repository": "^3.7.0", + "@loopback/service-proxy": "^3.2.1", + "@types/debug": "^4.1.5", + "@types/glob": "^7.1.3", + "debug": "^4.3.1", + "glob": "^7.1.7", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1" + } + }, + "node_modules/@loopback/build": { + "version": "6.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@loopback/eslint-config": "^10.2.1", + "@types/mocha": "^8.2.2", + "@types/node": "^10.17.60", + "cross-spawn": "^7.0.3", + "debug": "^4.3.1", + "eslint": "^7.28.0", + "fs-extra": "^10.0.0", + "glob": "^7.1.7", + "lodash": "^4.17.21", + "mocha": "^9.0.0", + "nyc": "^15.1.0", + "prettier": "^2.3.1", + "rimraf": "^3.0.2", + "source-map-support": "^0.5.19", + "typescript": "~4.3.2" + }, + "bin": { + "lb-clean": "bin/run-clean.js", + "lb-eslint": "bin/run-eslint.js", + "lb-mocha": "bin/run-mocha.js", + "lb-nyc": "bin/run-nyc.js", + "lb-prettier": "bin/run-prettier.js", + "lb-tsc": "bin/compile-package.js" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + } + }, + "node_modules/@loopback/context": { + "version": "3.17.0", + "license": "MIT", + "dependencies": { + "@loopback/metadata": "^3.3.1", + "@types/debug": "^4.1.5", + "debug": "^4.3.1", + "hyperid": "^2.1.0", + "p-event": "^4.2.0", + "tslib": "^2.2.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + } + }, + "node_modules/@loopback/core": { + "version": "2.16.1", + "license": "MIT", + "dependencies": { + "@loopback/context": "^3.17.0", + "debug": "^4.3.1", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + } + }, + "node_modules/@loopback/eslint-config": { + "version": "10.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "^4.26.1", + "@typescript-eslint/parser": "^4.26.1", + "eslint-config-prettier": "^8.3.0", + "eslint-plugin-eslint-plugin": "^3.0.3", + "eslint-plugin-mocha": "^9.0.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "eslint": "^7.28.0" + } + }, + "node_modules/@loopback/express": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "@loopback/http-server": "^2.5.1", + "@types/body-parser": "^1.19.0", + "@types/express": "^4.17.12", + "@types/express-serve-static-core": "^4.17.21", + "@types/http-errors": "^1.8.0", + "body-parser": "^1.19.0", + "debug": "^4.3.1", + "express": "^4.17.1", + "http-errors": "^1.8.0", + "on-finished": "^2.3.0", + "toposort": "^2.0.2", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1" + } + }, + "node_modules/@loopback/filter": { + "version": "1.5.1", + "license": "MIT", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + } + }, + "node_modules/@loopback/http-server": { + "version": "2.5.1", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "stoppable": "^1.1.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + } + }, + "node_modules/@loopback/metadata": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "debug": "^4.3.1", + "lodash": "^4.17.21", + "reflect-metadata": "^0.1.13", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + } + }, + "node_modules/@loopback/model-api-builder": { + "version": "2.3.1", + "license": "MIT", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1", + "@loopback/repository": "^3.7.0" + } + }, + "node_modules/@loopback/openapi-v3": { + "version": "5.3.1", + "license": "MIT", + "dependencies": { + "@loopback/repository-json-schema": "^3.4.1", + "debug": "^4.3.1", + "http-status": "^1.5.0", + "json-merge-patch": "^1.0.1", + "lodash": "^4.17.21", + "openapi3-ts": "^2.0.1", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1" + } + }, + "node_modules/@loopback/repository": { + "version": "3.7.0", + "license": "MIT", + "dependencies": { + "@loopback/filter": "^1.5.1", + "@types/debug": "^4.1.5", + "debug": "^4.3.1", + "lodash": "^4.17.21", + "loopback-datasource-juggler": "^4.26.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1" + } + }, + "node_modules/@loopback/repository-json-schema": { + "version": "3.4.1", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.7", + "debug": "^4.3.1", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1", + "@loopback/repository": "^3.7.0" + } + }, + "node_modules/@loopback/rest": { + "version": "9.3.1", + "license": "MIT", + "dependencies": { + "@loopback/express": "^3.3.1", + "@loopback/http-server": "^2.5.1", + "@loopback/openapi-v3": "^5.3.1", + "@openapi-contrib/openapi-schema-to-json-schema": "^3.1.0", + "@types/body-parser": "^1.19.0", + "@types/cors": "^2.8.10", + "@types/express": "^4.17.12", + "@types/express-serve-static-core": "^4.17.21", + "@types/http-errors": "^1.8.0", + "@types/on-finished": "^2.3.1", + "@types/serve-static": "1.13.9", + "@types/type-is": "^1.6.3", + "ajv": "^6.12.6", + "ajv-errors": "^1.0.1", + "ajv-keywords": "^3.5.2", + "body-parser": "^1.19.0", + "cors": "^2.8.5", + "debug": "^4.3.1", + "express": "^4.17.1", + "http-errors": "^1.8.0", + "js-yaml": "^4.1.0", + "json-schema-compare": "^0.2.2", + "lodash": "^4.17.21", + "on-finished": "^2.3.0", + "path-to-regexp": "^6.2.0", + "qs": "^6.10.1", + "strong-error-handler": "^4.0.0", + "tslib": "^2.2.0", + "type-is": "^1.6.18", + "validator": "^13.6.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1" + } + }, + "node_modules/@loopback/rest-explorer": { + "version": "3.3.1", + "license": "MIT", + "dependencies": { + "ejs": "^3.1.6", + "swagger-ui-dist": "3.50.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1", + "@loopback/rest": "^9.3.1" + } + }, + "node_modules/@loopback/service-proxy": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "loopback-datasource-juggler": "^4.26.0", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + }, + "peerDependencies": { + "@loopback/core": "^2.16.1" + } + }, + "node_modules/@loopback/testlab": { + "version": "3.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@hapi/shot": "^5.0.5", + "@types/express": "^4.17.12", + "@types/fs-extra": "^9.0.11", + "@types/shot": "^4.0.0", + "@types/sinon": "^10.0.2", + "@types/supertest": "^2.0.11", + "express": "^4.17.1", + "fs-extra": "^10.0.0", + "oas-validator": "^5.0.6", + "should": "^13.2.3", + "sinon": "^11.1.1", + "supertest": "^6.1.3", + "tslib": "^2.2.0" + }, + "engines": { + "node": "^10.16 || 12 || 14 || 16" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@openapi-contrib/openapi-schema-to-json-schema": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + } + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "7.1.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.7.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "6.0.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, + "node_modules/@types/body-parser": { + "version": "1.19.0", + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/body-parser/node_modules/@types/node": { + "version": "15.12.2", + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.34", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect/node_modules/@types/node": { + "version": "15.12.2", + "license": "MIT" + }, + "node_modules/@types/cookiejar": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/cors": { + "version": "2.8.10", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.5", + "license": "MIT" + }, + "node_modules/@types/express": { + "version": "4.17.12", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.21", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, + "node_modules/@types/express-serve-static-core/node_modules/@types/node": { + "version": "15.12.2", + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "9.0.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.1.3", + "license": "MIT", + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/glob/node_modules/@types/node": { + "version": "15.12.2", + "license": "MIT" + }, + "node_modules/@types/http-errors": { + "version": "1.8.0", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.7", + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "license": "MIT" + }, + "node_modules/@types/minimatch": { + "version": "3.0.4", + "license": "MIT" + }, + "node_modules/@types/mocha": { + "version": "8.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "10.17.60", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/on-finished": { + "version": "2.3.1", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/on-finished/node_modules/@types/node": { + "version": "15.12.2", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.9.6", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.3", + "license": "MIT" + }, + "node_modules/@types/serve-static": { + "version": "1.13.9", + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static/node_modules/@types/node": { + "version": "15.12.2", + "license": "MIT" + }, + "node_modules/@types/shot": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/sinon": { + "version": "10.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinonjs/fake-timers": "^7.1.0" + } + }, + "node_modules/@types/superagent": { + "version": "4.1.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, + "node_modules/@types/supertest": { + "version": "2.0.11", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/superagent": "*" + } + }, + "node_modules/@types/type-is": { + "version": "1.6.3", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/type-is/node_modules/@types/node": { + "version": "15.12.2", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "4.26.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/experimental-utils": "4.26.1", + "@typescript-eslint/scope-manager": "4.26.1", + "debug": "^4.3.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.21", + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^4.0.0", + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "4.26.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.26.1", + "@typescript-eslint/types": "4.26.1", + "@typescript-eslint/typescript-estree": "4.26.1", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "4.26.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "4.26.1", + "@typescript-eslint/types": "4.26.1", + "@typescript-eslint/typescript-estree": "4.26.1", + "debug": "^4.3.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^5.0.0 || ^6.0.0 || ^7.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "4.26.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "4.26.1", + "@typescript-eslint/visitor-keys": "4.26.1" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "4.26.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "4.26.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "4.26.1", + "@typescript-eslint/visitor-keys": "4.26.1", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "4.26.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "4.26.1", + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^8.10.0 || ^10.13.0 || >=11.10.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "dev": true, + "license": "ISC" + }, + "node_modules/accept-language": { + "version": "3.0.18", + "license": "MIT", + "dependencies": { + "bcp47": "^1.1.2", + "stable": "^0.1.6" + } + }, + "node_modules/accepts": { + "version": "1.3.7", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "7.4.1", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "1.0.1", + "license": "MIT", + "peerDependencies": { + "ajv": ">=5.0.0" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-transform": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "1.0.10", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/array-union": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/asn1": { + "version": "0.2.4", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.0", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.11.0", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/bcp47": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "2.2.1", + "license": "MIT", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.19.0", + "license": "MIT", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "1.7.2", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/inherits": { + "version": "2.0.3", + "license": "ISC" + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.7.0", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "dev": true, + "license": "ISC" + }, + "node_modules/browserslist": { + "version": "4.16.6", + "dev": true, + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-me-maybe": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camelcase": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001236", + "dev": true, + "license": "CC-BY-4.0", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + } + }, + "node_modules/capital-case": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "license": "Apache-2.0" + }, + "node_modules/chalk": { + "version": "2.4.2", + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/change-case": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.1", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/cldrjs": { + "version": "0.5.5" + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "1.2.2", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "license": "MIT" + }, + "node_modules/constant-case": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/content-type": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.1" + } + }, + "node_modules/convert-source-map/node_modules/safe-buffer": { + "version": "5.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.4.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/cookiejar": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "4.3.1", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-equal": { + "version": "2.0.5", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "es-get-iterator": "^1.1.1", + "get-intrinsic": "^1.0.1", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.1.1", + "isarray": "^2.0.5", + "object-is": "^1.1.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/default-require-extensions": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "license": "MIT" + }, + "node_modules/diff": { + "version": "5.0.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.6", + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.6.1" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.3.752", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enquirer": { + "version": "2.3.6", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/es-abstract": { + "version": "1.18.3", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "license": "MIT" + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.3.0", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-eslint-plugin": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-utils": "^2.1.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.0.0" + } + }, + "node_modules/eslint-plugin-eslint-plugin/node_modules/eslint-utils": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint-plugin-eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint-plugin-mocha": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-utils": "^3.0.0", + "ramda": "^0.27.1" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-utils": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "3.14.1", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.2.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.2.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/execa": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/express": { + "version": "4.17.1", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/express/node_modules/path-to-regexp": { + "version": "0.1.7", + "license": "MIT" + }, + "node_modules/express/node_modules/qs": { + "version": "6.7.0", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/express/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.0.7", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.11.0", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/filelist": { + "version": "1.0.2", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/find-cache-dir": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/foreach": { + "version": "2.0.5", + "license": "MIT" + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "1.2.2", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fromentries": { + "version": "1.3.2", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/fs-extra": { + "version": "10.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/glob": { + "version": "7.1.7", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globalize": { + "version": "1.6.0", + "license": "MIT", + "dependencies": { + "cldrjs": "^0.5.4" + } + }, + "node_modules/globals": { + "version": "13.9.0", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.6", + "dev": true, + "license": "ISC" + }, + "node_modules/growl": { + "version": "1.10.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.x" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has": { + "version": "1.0.3", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.1", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "license": "MIT", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "1.8.0", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-errors/node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" + }, + "node_modules/http-signature": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/http-status": { + "version": "1.5.0", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/http2-client": { + "version": "1.3.3", + "dev": true, + "license": "MIT" + }, + "node_modules/human-signals": { + "version": "1.1.1", + "license": "Apache-2.0", + "engines": { + "node": ">=8.12.0" + } + }, + "node_modules/hyperid": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "uuid": "^3.4.0", + "uuid-parse": "^1.1.0" + } + }, + "node_modules/hyperid/node_modules/uuid": { + "version": "3.4.0", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.1.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflection": { + "version": "1.13.1", + "engines": [ + "node >= 0.4.0" + ], + "license": "MIT" + }, + "node_modules/inflight": { + "version": "1.0.6", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/invert-kv": { + "version": "3.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sindresorhus/invert-kv?sponsor=1" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "license": "MIT" + }, + "node_modules/is-callable": { + "version": "1.2.3", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.5", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-string": { + "version": "1.0.6", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.5", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.2", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.0-next.2", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/isstream": { + "version": "0.1.2", + "license": "MIT" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.0", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.0", + "istanbul-lib-coverage": "^3.0.0-alpha.1", + "make-dir": "^3.0.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^3.3.3" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "3.4.0", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-reports": { + "version": "3.0.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.8.2", + "license": "Apache-2.0", + "dependencies": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/jake/node_modules/async": { + "version": "0.9.2", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "license": "Python-2.0" + }, + "node_modules/js2xmlparser": { + "version": "4.0.1", + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.3" + } + }, + "node_modules/jsbn": { + "version": "0.1.1", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-merge-patch": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "deep-equal": "^2.0.3" + } + }, + "node_modules/json-schema": { + "version": "0.2.3" + }, + "node_modules/json-schema-compare": { + "version": "0.2.2", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.4" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "license": "ISC" + }, + "node_modules/json5": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonpath-plus": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/jsprim": { + "version": "1.4.1", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/lcid": { + "version": "3.1.1", + "license": "MIT", + "dependencies": { + "invert-kv": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loopback-connector": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "async": "^3.2.0", + "bluebird": "^3.7.2", + "debug": "^4.1.1", + "msgpack5": "^4.2.0", + "strong-globalize": "^6.0.4", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/loopback-connector-rest": { + "version": "3.7.0", + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "jsonpath-plus": "^1.1.0", + "lodash": "^4.17.11", + "methods": "^1.1.1", + "mime": "^2.3.1", + "qs": "^6.1.0", + "request": "^2.53.0", + "strong-globalize": "^5.0.5", + "traverse": "^0.6.6" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/loopback-connector-rest/node_modules/mime": { + "version": "2.5.2", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/loopback-connector-rest/node_modules/mkdirp": { + "version": "0.5.5", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/loopback-connector-rest/node_modules/strong-globalize": { + "version": "5.1.0", + "license": "Artistic-2.0", + "dependencies": { + "accept-language": "^3.0.18", + "debug": "^4.1.1", + "globalize": "^1.5.0", + "lodash": "^4.17.15", + "md5": "^2.2.1", + "mkdirp": "^0.5.5", + "os-locale": "^5.0.0", + "yamljs": "^0.3.0" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/loopback-datasource-juggler": { + "version": "4.26.0", + "license": "MIT", + "dependencies": { + "async": "^3.1.0", + "change-case": "^4.1.1", + "debug": "^4.1.0", + "depd": "^2.0.0", + "inflection": "^1.6.0", + "lodash": "^4.17.11", + "loopback-connector": "^5.0.0", + "minimatch": "^3.0.3", + "qs": "^6.5.0", + "shortid": "^2.2.6", + "strong-globalize": "^6.0.5", + "traverse": "^0.6.6", + "uuid": "^8.3.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lower-case": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "license": "MIT", + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/md5": { + "version": "2.3.0", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mem": { + "version": "5.1.1", + "license": "MIT", + "dependencies": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^2.1.0", + "p-is-promise": "^2.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.48.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.31", + "license": "MIT", + "dependencies": { + "mime-db": "1.48.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "9.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.1", + "debug": "4.3.1", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.1.7", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "nanoid": "3.1.23", + "serialize-javascript": "5.0.1", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "which": "2.0.2", + "wide-align": "1.1.3", + "workerpool": "6.1.4", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mocha/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/nanoid": { + "version": "3.1.23", + "dev": true, + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/msgpack5": { + "version": "4.5.1", + "license": "MIT", + "dependencies": { + "bl": "^2.0.1", + "inherits": "^2.0.3", + "readable-stream": "^2.3.6", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/nanoid": { + "version": "2.1.11", + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nise": { + "version": "5.1.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.7.0", + "@sinonjs/fake-timers": "^7.0.4", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + } + }, + "node_modules/nise/node_modules/isarray": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/no-case": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/node-fetch-h2": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "http2-client": "^1.2.5" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-preload": { + "version": "0.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "1.1.73", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc": { + "version": "15.1.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/nyc/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/nyc/node_modules/decamelize": { + "version": "1.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nyc/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nyc/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "dev": true, + "license": "ISC" + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/oas-kit-common": { + "version": "1.0.8", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "fast-safe-stringify": "^2.0.7" + } + }, + "node_modules/oas-linter": { + "version": "3.2.2", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@exodus/schemasafe": "^1.0.0-rc.2", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver": { + "version": "2.5.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "node-fetch-h2": "^2.3.0", + "oas-kit-common": "^1.0.8", + "reftools": "^1.1.8", + "yaml": "^1.10.0", + "yargs": "^17.0.1" + }, + "bin": { + "resolve": "resolve.js" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-resolver/node_modules/yargs": { + "version": "17.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/oas-schema-walker": { + "version": "1.1.5", + "dev": true, + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oas-validator": { + "version": "5.0.6", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "call-me-maybe": "^1.0.1", + "oas-kit-common": "^1.0.8", + "oas-linter": "^3.2.2", + "oas-resolver": "^2.5.5", + "oas-schema-walker": "^1.1.5", + "reftools": "^1.1.8", + "should": "^13.2.1", + "yaml": "^1.10.0" + }, + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.10.3", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.2", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openapi3-ts": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "yaml": "^1.10.0" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/os-locale": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "execa": "^4.0.0", + "lcid": "^3.0.0", + "mem": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-defer": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-event": { + "version": "4.2.0", + "license": "MIT", + "dependencies": { + "p-timeout": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-finally": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/p-is-promise": { + "version": "2.1.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-timeout": { + "version": "3.2.0", + "license": "MIT", + "dependencies": { + "p-finally": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-hash": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "6.2.0", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/pg": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", + "dependencies": { + "pg-connection-string": "^2.11.0", + "pg-pool": "^3.11.0", + "pg-protocol": "^1.11.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.3.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", + "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.11.0.tgz", + "integrity": "sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.11.0.tgz", + "integrity": "sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "dependencies": { + "split2": "^4.1.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "license": "MIT" + }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.10.1", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/ramda": { + "version": "0.27.1", + "dev": true, + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "license": "MIT", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "1.7.2", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body/node_modules/inherits": { + "version": "2.0.3", + "license": "ISC" + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "license": "Apache-2.0" + }, + "node_modules/reftools": { + "version": "1.1.8", + "dev": true, + "license": "BSD-3-Clause", + "funding": { + "url": "https://github.com/Mermade/oas-kit?sponsor=1" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.3.1", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexpp": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/request": { + "version": "2.88.2", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.2", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.3.5", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.17.1", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/send/node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/http-errors": { + "version": "1.7.3", + "license": "MIT", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "license": "MIT" + }, + "node_modules/sentence-case": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/serialize-javascript": { + "version": "5.0.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-static": { + "version": "1.14.1", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shortid": { + "version": "2.2.16", + "license": "MIT", + "dependencies": { + "nanoid": "^2.1.0" + } + }, + "node_modules/should": { + "version": "13.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "node_modules/should-equal": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.4.0" + } + }, + "node_modules/should-format": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "node_modules/should-type": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "node_modules/should-type-adaptors": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "node_modules/should-util": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "license": "ISC" + }, + "node_modules/sinon": { + "version": "11.1.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^7.1.0", + "@sinonjs/samsam": "^6.0.2", + "diff": "^5.0.0", + "nise": "^5.1.0", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/snake-case": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/source-map": { + "version": "0.5.7", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.19", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "engines": { + "node": ">= 10.x" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "license": "BSD-3-Clause" + }, + "node_modules/sshpk": { + "version": "1.16.1", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable": { + "version": "0.1.8", + "license": "MIT" + }, + "node_modules/statuses": { + "version": "1.5.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" + }, + "node_modules/string-width": { + "version": "4.2.2", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.4", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strong-error-handler": { + "version": "4.0.0", + "license": "MIT", + "dependencies": { + "@types/express": "^4.16.0", + "accepts": "^1.3.3", + "debug": "^4.1.1", + "ejs": "^3.1.3", + "fast-safe-stringify": "^2.0.6", + "http-status": "^1.1.2", + "js2xmlparser": "^4.0.0", + "strong-globalize": "^6.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/strong-globalize": { + "version": "6.0.5", + "license": "Artistic-2.0", + "dependencies": { + "accept-language": "^3.0.18", + "debug": "^4.2.0", + "globalize": "^1.6.0", + "lodash": "^4.17.20", + "md5": "^2.3.0", + "mkdirp": "^1.0.4", + "os-locale": "^5.0.0", + "yamljs": "^0.3.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/superagent": { + "version": "6.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + }, + "engines": { + "node": ">= 7.0.0" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.5.2", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/superagent/node_modules/readable-stream": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/supertest": { + "version": "6.1.3", + "dev": true, + "license": "MIT", + "dependencies": { + "methods": "^1.1.2", + "superagent": "^6.1.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/swagger-ui-dist": { + "version": "3.50.0", + "license": "Apache-2.0" + }, + "node_modules/table": { + "version": "6.7.1", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.clonedeep": "^4.5.0", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/table/node_modules/ajv": { + "version": "8.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/table/node_modules/json-schema-traverse": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/toposort": { + "version": "2.0.2", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/traverse": { + "version": "0.6.6", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.2.0", + "license": "0BSD" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "dev": true, + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "license": "Unlicense" + }, + "node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/typescript": { + "version": "4.3.2", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/upper-case": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uuid-parse": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/validator": { + "version": "13.6.0", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/which-typed-array": { + "version": "1.1.4", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.2", + "call-bind": "^1.0.0", + "es-abstract": "^1.18.0-next.1", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.3", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workerpool": { + "version": "6.1.4", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xmlcreate": { + "version": "2.0.3", + "license": "Apache-2.0" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yamljs": { + "version": "0.3.0", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "glob": "^7.0.5" + }, + "bin": { + "json2yaml": "bin/json2yaml", + "yaml2json": "bin/yaml2json" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/PROD_STAND/POSTGRES/testnode/package.json b/PROD_STAND/POSTGRES/testnode/package.json new file mode 100644 index 0000000..fa2e5d5 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/package.json @@ -0,0 +1,69 @@ +{ + "name": "loopback4-example-github", + "version": "0.0.1", + "description": "loopback4-example-github", + "keywords": [ + "loopback-application", + "loopback" + ], + "main": "server.js", + "engines": { + "node": ">=10.16" + }, + "scripts": { + "build": "lb-tsc", + "build:watch": "lb-tsc --watch", + "lint": "npm run eslint && npm run prettier:check", + "lint:fix": "npm run eslint:fix && npm run prettier:fix", + "prettier:cli": "lb-prettier \"**/*.ts\" \"**/*.js\"", + "prettier:check": "npm run prettier:cli -- -l", + "prettier:fix": "npm run prettier:cli -- --write", + "eslint": "lb-eslint --report-unused-disable-directives .", + "eslint:fix": "npm run eslint -- --fix", + "pretest": "npm run rebuild", + "test": "lb-mocha --allow-console-logs \"dist/__tests__\"", + "posttest": "npm run lint", + "test:dev": "lb-mocha --allow-console-logs dist/__tests__/**/*.js && npm run posttest", + "docker:build": "docker build -t loopback4-example-github .", + "docker:run": "docker run -p 3000:3000 -d loopback4-example-github", + "premigrate": "npm run build", + "migrate": "node ./dist/migrate", + "preopenapi-spec": "npm run build", + "openapi-spec": "node ./dist/openapi-spec", + "start": "node server.js", + "clean": "lb-clean dist *.tsbuildinfo .eslintcache", + "rebuild": "npm run clean && npm run build" + }, + "repository": { + "type": "git", + "url": "" + }, + "author": "Diana Lau ", + "license": "", + "files": [ + "README.md", + "dist", + "src", + "!*/__tests__" + ], + "dependencies": { + "@loopback/boot": "^3.4.1", + "@loopback/core": "^2.16.1", + "@loopback/repository": "^3.7.0", + "@loopback/rest": "^9.3.1", + "@loopback/rest-explorer": "^3.3.1", + "@loopback/service-proxy": "^3.2.1", + "loopback-connector-rest": "^3.7.0", + "pg": "^8.18.0", + "tslib": "^2.0.0" + }, + "devDependencies": { + "@loopback/build": "^6.4.1", + "@loopback/eslint-config": "^10.2.1", + "@loopback/testlab": "^3.4.1", + "@types/node": "^10.17.60", + "eslint": "^7.28.0", + "source-map-support": "^0.5.19", + "typescript": "~4.3.2" + } +} diff --git a/PROD_STAND/POSTGRES/testnode/public/index.html b/PROD_STAND/POSTGRES/testnode/public/index.html new file mode 100644 index 0000000..2b8f97e --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/public/index.html @@ -0,0 +1,88 @@ + + + + + loopback4-example-github + + + + + + + + + + +
+

loopback4-example-github

+

Version 1.0.0

+ +

OpenAPI spec: /openapi.json

+

API Explorer: /explorer

+
+ + + + + diff --git a/PROD_STAND/POSTGRES/testnode/server.js b/PROD_STAND/POSTGRES/testnode/server.js new file mode 100644 index 0000000..d8c0fea --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/server.js @@ -0,0 +1,204 @@ +const http = require("http"); +const { URL } = require("url"); +const { Pool } = require("pg"); + +// Конфигурация Postgres, DATABASE_URL имеет приоритет. +function buildPgConfig() { + if (process.env.DATABASE_URL) { + return { + connectionString: process.env.DATABASE_URL, + ssl: process.env.PGSSLMODE === "require" ? { rejectUnauthorized: false } : undefined, + }; + } + + return { + host: process.env.PGHOST, + port: Number(process.env.PGPORT || "5432"), + user: process.env.PGUSER, + password: process.env.PGPASSWORD, + database: process.env.PGDATABASE || "postgres", + ssl: process.env.PGSSLMODE === "require" ? { rejectUnauthorized: false } : undefined, + }; +} + +// Общий пул подключений. +const pool = new Pool(buildPgConfig()); + +// Таблица для демо создается автоматически. +async function ensureTable() { + await pool.query( + "CREATE TABLE IF NOT EXISTS nubes_test_table (id SERIAL PRIMARY KEY, test_data TEXT, created_at TIMESTAMP DEFAULT NOW())" + ); +} + +// Простой парсер application/x-www-form-urlencoded. +function parseForm(body) { + return body + .split("&") + .map((pair) => pair.split("=")) + .reduce((acc, [key, value]) => { + acc[decodeURIComponent(key)] = decodeURIComponent((value || "").replace(/\+/g, " ")); + return acc; + }, {}); +} + +// Рендер HTML-страницы с CRUD-формами. +function renderPage(rows, error) { + const errorHtml = error ? `

${error}

` : ""; + const rowsHtml = rows + .map( + (row) => ` + + ${row.id} + +
+ + + +
+ + +
+ + +
+ + ` + ) + .join(""); + + return ` + + + + NodeJS + Postgres Demo + + + + +
+
+ +
NodeJS + Postgres Demo
+
+
+
+
+ ${errorHtml} +
+ + +
+ + + + ${rowsHtml} + +
IDContentActions
+
+
+ +`; +} + +// Маршруты: /, /add, /update, /delete, /healthz. +async function handleRequest(req, res) { + const url = new URL(req.url, `http://${req.headers.host}`); + + if (req.method === "GET" && url.pathname === "/healthz") { + res.writeHead(200, { "Content-Type": "text/plain" }); + res.end("ok"); + return; + } + + if (req.method === "GET" && url.pathname === "/") { + try { + await ensureTable(); + const { rows } = await pool.query( + "SELECT id, test_data FROM nubes_test_table ORDER BY id DESC LIMIT 20" + ); + res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + res.end(renderPage(rows)); + } catch (err) { + res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" }); + res.end(renderPage([], err.message)); + } + return; + } + + if (req.method === "POST" && ["/add", "/update", "/delete"].includes(url.pathname)) { + let body = ""; + req.on("data", (chunk) => { + body += chunk; + }); + req.on("end", async () => { + const form = parseForm(body); + try { + await ensureTable(); + if (url.pathname === "/add") { + const content = form.txt_content || "Node did it"; + // Защита от быстрых дублей подряд. + const { rows: lastRows } = await pool.query( + "SELECT test_data, created_at FROM nubes_test_table ORDER BY id DESC LIMIT 1" + ); + const last = lastRows[0]; + const isDuplicate = + last && + last.test_data === content && + Date.now() - new Date(last.created_at).getTime() < 3000; + + if (!isDuplicate) { + await pool.query("INSERT INTO nubes_test_table (test_data) VALUES ($1)", [content]); + } + } + if (url.pathname === "/update") { + await pool.query("UPDATE nubes_test_table SET test_data=$1 WHERE id=$2", [ + form.txt_content || "", + form.id, + ]); + } + if (url.pathname === "/delete") { + await pool.query("DELETE FROM nubes_test_table WHERE id=$1", [form.id]); + } + res.writeHead(303, { Location: "/" }); + res.end(); + } catch (err) { + res.writeHead(500, { "Content-Type": "text/html; charset=utf-8" }); + res.end(renderPage([], err.message)); + } + }); + return; + } + + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("Not found"); +} + +const port = process.env.PORT || 3000; +const server = http.createServer(handleRequest); + +server.listen(port, () => { + console.log(`Server running on port ${port}`); +}); diff --git a/PROD_STAND/POSTGRES/testnode/src/__tests__/README.md b/PROD_STAND/POSTGRES/testnode/src/__tests__/README.md new file mode 100644 index 0000000..a88f8a5 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/__tests__/README.md @@ -0,0 +1,3 @@ +# Tests + +Please place your tests in this folder. diff --git a/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/home-page.acceptance.ts b/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/home-page.acceptance.ts new file mode 100644 index 0000000..2372b04 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/home-page.acceptance.ts @@ -0,0 +1,31 @@ +import {Client} from '@loopback/testlab'; +import {Loopback4ExampleGithubApplication} from '../..'; +import {setupApplication} from './test-helper'; + +describe('HomePage', () => { + let app: Loopback4ExampleGithubApplication; + let client: Client; + + before('setupApplication', async () => { + ({app, client} = await setupApplication()); + }); + + after(async () => { + await app.stop(); + }); + + it('exposes a default home page', async () => { + await client + .get('/') + .expect(200) + .expect('Content-Type', /text\/html/); + }); + + it('exposes self-hosted explorer', async () => { + await client + .get('/explorer/') + .expect(200) + .expect('Content-Type', /text\/html/) + .expect(/LoopBack API Explorer/); + }); +}); diff --git a/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/ping.controller.acceptance.ts b/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/ping.controller.acceptance.ts new file mode 100644 index 0000000..3d56b1b --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/ping.controller.acceptance.ts @@ -0,0 +1,21 @@ +import {Client, expect} from '@loopback/testlab'; +import {Loopback4ExampleGithubApplication} from '../..'; +import {setupApplication} from './test-helper'; + +describe('PingController', () => { + let app: Loopback4ExampleGithubApplication; + let client: Client; + + before('setupApplication', async () => { + ({app, client} = await setupApplication()); + }); + + after(async () => { + await app.stop(); + }); + + it('invokes GET /ping', async () => { + const res = await client.get('/ping?msg=world').expect(200); + expect(res.body).to.containEql({greeting: 'Hello from LoopBack'}); + }); +}); diff --git a/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/test-helper.ts b/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/test-helper.ts new file mode 100644 index 0000000..c457c53 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/__tests__/acceptance/test-helper.ts @@ -0,0 +1,32 @@ +import {Loopback4ExampleGithubApplication} from '../..'; +import { + createRestAppClient, + givenHttpServerConfig, + Client, +} from '@loopback/testlab'; + +export async function setupApplication(): Promise<AppWithClient> { + const restConfig = givenHttpServerConfig({ + // Customize the server configuration here. + // Empty values (undefined, '') will be ignored by the helper. + // + // host: process.env.HOST, + // port: +process.env.PORT, + }); + + const app = new Loopback4ExampleGithubApplication({ + rest: restConfig, + }); + + await app.boot(); + await app.start(); + + const client = createRestAppClient(app); + + return {app, client}; +} + +export interface AppWithClient { + app: Loopback4ExampleGithubApplication; + client: Client; +} diff --git a/PROD_STAND/POSTGRES/testnode/src/application.ts b/PROD_STAND/POSTGRES/testnode/src/application.ts new file mode 100644 index 0000000..d803e96 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/application.ts @@ -0,0 +1,44 @@ +import {BootMixin} from '@loopback/boot'; +import {ApplicationConfig} from '@loopback/core'; +import { + RestExplorerBindings, + RestExplorerComponent, +} from '@loopback/rest-explorer'; +import {RepositoryMixin} from '@loopback/repository'; +import {RestApplication} from '@loopback/rest'; +import {ServiceMixin} from '@loopback/service-proxy'; +import path from 'path'; +import {MySequence} from './sequence'; + +export {ApplicationConfig}; + +export class Loopback4ExampleGithubApplication extends BootMixin( + ServiceMixin(RepositoryMixin(RestApplication)), +) { + constructor(options: ApplicationConfig = {}) { + super(options); + + // Подключаем кастомную sequence для обработки запросов. + this.sequence(MySequence); + + // Главная страница из папки public. + this.static('/', path.join(__dirname, '../public')); + + // Включаем REST Explorer. + this.configure(RestExplorerBindings.COMPONENT).to({ + path: '/explorer', + }); + this.component(RestExplorerComponent); + + this.projectRoot = __dirname; + // Настройки автозагрузки контроллеров. + this.bootOptions = { + controllers: { + // Ищем контроллеры в папке controllers. + dirs: ['controllers'], + extensions: ['.controller.js'], + nested: true, + }, + }; + } +} diff --git a/PROD_STAND/POSTGRES/testnode/src/controllers/README.md b/PROD_STAND/POSTGRES/testnode/src/controllers/README.md new file mode 100644 index 0000000..ad4c4cc --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/controllers/README.md @@ -0,0 +1,9 @@ +# Controllers + +This directory contains source files for the controllers exported by this app. + +To add a new empty controller, type in `lb4 controller [<name>]` from the +command-line of your application's root directory. + +For more information, please visit +[Controller generator](http://loopback.io/doc/en/lb4/Controller-generator.html). diff --git a/PROD_STAND/POSTGRES/testnode/src/controllers/gh-query.controller.ts b/PROD_STAND/POSTGRES/testnode/src/controllers/gh-query.controller.ts new file mode 100644 index 0000000..a3096ca --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/controllers/gh-query.controller.ts @@ -0,0 +1,134 @@ +// Uncomment these imports to begin using these cool features! + +import {inject} from '@loopback/context'; +import {get, getModelSchemaRef, param} from '@loopback/openapi-v3'; +import {QueryResult, ResultIssueInfo} from '../models'; +import {GhQueryService, IssueInfo, QueryResponse} from '../services'; + +// import {inject} from '@loopback/core'; + + +export class GhQueryController { + // inject the GhQueryService service proxy + constructor(@inject('services.GhQueryService') protected queryService:GhQueryService) {} + + // create the API that get the issues by providing: + // repo: <GitHub org>/<GitHub repo>. For example, `strongloop/loopback-next` + // label: If it has special characters, you need to escape it. + // For example, if the label is "help wanted", it will be "help+wanted". + @get('/issues/repo/{repo}/label/{label}', { + responses: { + '200': { + description: 'Array of GitHub issues info', + content: { + 'application/json': { + schema: getModelSchemaRef(QueryResult) + } + } + } + } + }) + async getIssuesByLabel( + @param.path.string('repo') repo: string, + @param.path.string('label') label:string): Promise<QueryResult> { + let result:QueryResponse = await this.queryService.getIssuesByLabel(repo, label); + let queryResult = new QueryResult(); + queryResult.items = []; + queryResult.total_count = result.body.total_count; + result.body.items.forEach(issue => { + this.addToResult(issue, queryResult); + }); + + // check if there is next page of the results + const nextLink = this.getNextLink(result.headers.link); + if (nextLink == null) return queryResult; + await this.getIssueByURL(nextLink, this.queryService, queryResult); + return queryResult; + } + + /** + * Get issues from URL + * @param nextLinkURL + * @param queryService + * @param queryResult + * @returns + */ + async getIssueByURL(nextLinkURL: string, queryService: GhQueryService, queryResult:QueryResult) { + let result = await queryService.getIssuesByURL(nextLinkURL); + result.body.items.forEach(issue => { + this.addToResult(issue, queryResult); + }); + + const nextLink2 = this.getNextLink(result.headers.link); + if (nextLink2 == null) return; + await this.getIssueByURL(nextLink2, queryService, queryResult); + } + + /** +* Get the URL for the "next" page. +* The Link header is in the format of: +* Link: <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel="next", + <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel="last" +* @param link +* @returns +*/ +getNextLink(link: string): string|null { + if (link == undefined) return null; + + let tokens: string[] = link.split(','); + let url: string|null = null; + + tokens.forEach(token => { + if (token.indexOf('rel="next"')!=-1) { + url = token.substring(token.indexOf('<')+1, token.indexOf(';')-1); + } + }); + + return url; + } + /** + * Add the issue to the QueryResult object + * @param issue + * @param queryResult + */ + addToResult(issue: IssueInfo, queryResult: QueryResult) { + let issueInfo:ResultIssueInfo = new ResultIssueInfo(); + issueInfo.html_url = issue.html_url; + issueInfo.title = issue.title; + issueInfo.state = issue.state; + issueInfo.age = this.getIssueAge(issue.created_at); + queryResult.items?.push(issueInfo); + } + /** + * Calculate the age of the issue + * i.e. take today's date and find the number of days difference from + * the issue creation date + * @param created_at + * @returns + */ + getIssueAge(created_at: string): number { + let todayDate: Date = new Date(); + let createDate: Date = new Date(created_at); + let differenceInTime = todayDate.getTime() - createDate.getTime(); + + //get the difference in day + return Math.floor(differenceInTime / (1000 * 3600 * 24)); + } +} + + + +// /** +// * QueryResult +// */ +// class QueryResult { +// total_count: number; +// items: ResultIssueInfo[]; +// } + +// class ResultIssueInfo { +// title: string; +// html_url: string; +// state: string; +// age: number; +// } diff --git a/PROD_STAND/POSTGRES/testnode/src/controllers/index.ts b/PROD_STAND/POSTGRES/testnode/src/controllers/index.ts new file mode 100644 index 0000000..41430f1 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/controllers/index.ts @@ -0,0 +1,2 @@ +export * from './ping.controller'; +export * from './gh-query.controller'; diff --git a/PROD_STAND/POSTGRES/testnode/src/controllers/ping.controller.ts b/PROD_STAND/POSTGRES/testnode/src/controllers/ping.controller.ts new file mode 100644 index 0000000..2ff11ed --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/controllers/ping.controller.ts @@ -0,0 +1,55 @@ +import {inject} from '@loopback/core'; +import { + Request, + RestBindings, + get, + response, + ResponseObject, +} from '@loopback/rest'; + +/** + * OpenAPI response for ping() + */ +const PING_RESPONSE: ResponseObject = { + description: 'Ping Response', + content: { + 'application/json': { + schema: { + type: 'object', + title: 'PingResponse', + properties: { + greeting: {type: 'string'}, + date: {type: 'string'}, + url: {type: 'string'}, + headers: { + type: 'object', + properties: { + 'Content-Type': {type: 'string'}, + }, + additionalProperties: true, + }, + }, + }, + }, + }, +}; + +/** + * A simple controller to bounce back http requests + */ +export class PingController { + constructor(@inject(RestBindings.Http.REQUEST) private req: Request) {} + + // Map to `GET /ping` + @get('/ping') + @response(200, PING_RESPONSE) + ping(): object { + // Reply with a greeting, the current time, the url, and request headers + return { + greeting: 'Hello from LoopBack', + date: new Date(), + url: this.req.url, + headers: Object.assign({}, this.req.headers), + }; + } +} diff --git a/PROD_STAND/POSTGRES/testnode/src/datasources/README.md b/PROD_STAND/POSTGRES/testnode/src/datasources/README.md new file mode 100644 index 0000000..57ae382 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/datasources/README.md @@ -0,0 +1,3 @@ +# Datasources + +This directory contains config for datasources used by this app. diff --git a/PROD_STAND/POSTGRES/testnode/src/datasources/githubds.datasource.ts b/PROD_STAND/POSTGRES/testnode/src/datasources/githubds.datasource.ts new file mode 100644 index 0000000..a42f7a9 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/datasources/githubds.datasource.ts @@ -0,0 +1,66 @@ +import {inject, lifeCycleObserver, LifeCycleObserver} from '@loopback/core'; +import {juggler} from '@loopback/repository'; + +const config = { + name: 'githubds', + connector: 'rest', + baseURL: 'https://api.github.ibm.com', + crud: false, + options: { + headers: { + accept: 'application/json', + Authorization: process.env.TOKEN, + 'User-Agent': 'loopback4-example-github', + 'X-RateLimit-Limit': 5000, + 'content-type': 'application/json' + } + }, + operations: [ + { + template: { + method: 'GET', + fullResponse: true, + url: 'https://api.github.com/search/issues?q=repo:{repo}+label:"{label}"' + }, + functions: { + getIssuesByLabel: ['repo','label'] + } + }, { + template: { + method: 'GET', + fullResponse: true, + url: '{url}' + }, + functions: { + getIssuesByURL: ['url'] + } + }, { + template: { + method: 'GET', + fullResponse: true, + url: 'https://api.github.com/search/issues?q=repo:{repo}+{querystring}' + }, + functions: { + getIssuesWithQueryString: ['repo','querystring'] + } + } + ] +}; + +// Observe application's life cycle to disconnect the datasource when +// application is stopped. This allows the application to be shut down +// gracefully. The `stop()` method is inherited from `juggler.DataSource`. +// Learn more at https://loopback.io/doc/en/lb4/Life-cycle.html +@lifeCycleObserver('datasource') +export class GithubdsDataSource extends juggler.DataSource + implements LifeCycleObserver { + static dataSourceName = 'githubds'; + static readonly defaultConfig = config; + + constructor( + @inject('datasources.config.githubds', {optional: true}) + dsConfig: object = config, + ) { + super(dsConfig); + } +} diff --git a/PROD_STAND/POSTGRES/testnode/src/datasources/index.ts b/PROD_STAND/POSTGRES/testnode/src/datasources/index.ts new file mode 100644 index 0000000..075ed6d --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/datasources/index.ts @@ -0,0 +1 @@ +export * from './githubds.datasource'; diff --git a/PROD_STAND/POSTGRES/testnode/src/index.ts b/PROD_STAND/POSTGRES/testnode/src/index.ts new file mode 100644 index 0000000..f7aa6bd --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/index.ts @@ -0,0 +1,40 @@ +import {ApplicationConfig, Loopback4ExampleGithubApplication} from './application'; + +export * from './application'; + +export async function main(options: ApplicationConfig = {}) { + // Создаем и запускаем LoopBack приложение. + const app = new Loopback4ExampleGithubApplication(options); + await app.boot(); + await app.start(); + + const url = app.restServer.url; + console.log(`Server is running at ${url}`); + console.log(`Try ${url}/ping`); + + return app; +} + +if (require.main === module) { + // Локальный запуск с базовой конфигурацией REST. + const config = { + rest: { + port: +(process.env.PORT ?? 3000), + host: process.env.HOST, + // The `gracePeriodForClose` provides a graceful close for http/https + // servers with keep-alive clients. The default value is `Infinity` + // (don't force-close). If you want to immediately destroy all sockets + // upon stop, set its value to `0`. + // See https://www.npmjs.com/package/stoppable + gracePeriodForClose: 5000, // 5 seconds + openApiSpec: { + // useful when used with OpenAPI-to-GraphQL to locate your application + setServersFromRequest: true, + }, + }, + }; + main(config).catch(err => { + console.error('Cannot start the application.', err); + process.exit(1); + }); +} diff --git a/PROD_STAND/POSTGRES/testnode/src/migrate.ts b/PROD_STAND/POSTGRES/testnode/src/migrate.ts new file mode 100644 index 0000000..7c5c806 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/migrate.ts @@ -0,0 +1,20 @@ +import {Loopback4ExampleGithubApplication} from './application'; + +export async function migrate(args: string[]) { + const existingSchema = args.includes('--rebuild') ? 'drop' : 'alter'; + console.log('Migrating schemas (%s existing schema)', existingSchema); + + const app = new Loopback4ExampleGithubApplication(); + await app.boot(); + await app.migrateSchema({existingSchema}); + + // Connectors usually keep a pool of opened connections, + // this keeps the process running even after all work is done. + // We need to exit explicitly. + process.exit(0); +} + +migrate(process.argv).catch(err => { + console.error('Cannot migrate database schema', err); + process.exit(1); +}); diff --git a/PROD_STAND/POSTGRES/testnode/src/models/README.md b/PROD_STAND/POSTGRES/testnode/src/models/README.md new file mode 100644 index 0000000..f5ea972 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/models/README.md @@ -0,0 +1,3 @@ +# Models + +This directory contains code for models provided by this app. diff --git a/PROD_STAND/POSTGRES/testnode/src/models/index.ts b/PROD_STAND/POSTGRES/testnode/src/models/index.ts new file mode 100644 index 0000000..7691854 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/models/index.ts @@ -0,0 +1,2 @@ +export * from './query-result.model'; +export * from './result-issue-info.model'; diff --git a/PROD_STAND/POSTGRES/testnode/src/models/query-result.model.ts b/PROD_STAND/POSTGRES/testnode/src/models/query-result.model.ts new file mode 100644 index 0000000..e0193c7 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/models/query-result.model.ts @@ -0,0 +1,24 @@ +import {Model, model, property} from '@loopback/repository'; +import {ResultIssueInfo} from './result-issue-info.model'; + +@model() +export class QueryResult extends Model { + @property({ + type: 'number', + }) + total_count?: number; + + @property.array(ResultIssueInfo) + items?: ResultIssueInfo[]; + + + constructor(data?: Partial<QueryResult>) { + super(data); + } +} + +export interface QueryResultRelations { + // describe navigational properties here +} + +export type QueryResultWithRelations = QueryResult & QueryResultRelations; diff --git a/PROD_STAND/POSTGRES/testnode/src/models/result-issue-info.model.ts b/PROD_STAND/POSTGRES/testnode/src/models/result-issue-info.model.ts new file mode 100644 index 0000000..3343f1a --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/models/result-issue-info.model.ts @@ -0,0 +1,35 @@ +import {Model, model, property} from '@loopback/repository'; + +@model() +export class ResultIssueInfo extends Model { + @property({ + type: 'string', + }) + title?: string; + + @property({ + type: 'string', + }) + html_url?: string; + + @property({ + type: 'string', + }) + state?: string; + + @property({ + type: 'number', + }) + age?: number; + + + constructor(data?: Partial<ResultIssueInfo>) { + super(data); + } +} + +export interface ResultIssueInfoRelations { + // describe navigational properties here +} + +export type ResultIssueInfoWithRelations = ResultIssueInfo & ResultIssueInfoRelations; diff --git a/PROD_STAND/POSTGRES/testnode/src/openapi-spec.ts b/PROD_STAND/POSTGRES/testnode/src/openapi-spec.ts new file mode 100644 index 0000000..e525841 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/openapi-spec.ts @@ -0,0 +1,23 @@ +import {ApplicationConfig} from '@loopback/core'; +import {Loopback4ExampleGithubApplication} from './application'; + +/** + * Export the OpenAPI spec from the application + */ +async function exportOpenApiSpec(): Promise<void> { + const config: ApplicationConfig = { + rest: { + port: +(process.env.PORT ?? 3000), + host: process.env.HOST ?? 'localhost', + }, + }; + const outFile = process.argv[2] ?? ''; + const app = new Loopback4ExampleGithubApplication(config); + await app.boot(); + await app.exportOpenApiSpec(outFile); +} + +exportOpenApiSpec().catch(err => { + console.error('Fail to export OpenAPI spec from the application.', err); + process.exit(1); +}); diff --git a/PROD_STAND/POSTGRES/testnode/src/repositories/README.md b/PROD_STAND/POSTGRES/testnode/src/repositories/README.md new file mode 100644 index 0000000..08638a7 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/repositories/README.md @@ -0,0 +1,3 @@ +# Repositories + +This directory contains code for repositories provided by this app. diff --git a/PROD_STAND/POSTGRES/testnode/src/sequence.ts b/PROD_STAND/POSTGRES/testnode/src/sequence.ts new file mode 100644 index 0000000..30b28d6 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/sequence.ts @@ -0,0 +1,4 @@ +import {MiddlewareSequence} from '@loopback/rest'; + +// Стандартная sequence без кастомной логики. +export class MySequence extends MiddlewareSequence {} diff --git a/PROD_STAND/POSTGRES/testnode/src/services/gh-query-service.service.ts b/PROD_STAND/POSTGRES/testnode/src/services/gh-query-service.service.ts new file mode 100644 index 0000000..99e0e25 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/services/gh-query-service.service.ts @@ -0,0 +1,45 @@ +import {inject, Provider} from '@loopback/core'; +import {getService} from '@loopback/service-proxy'; +import {GithubdsDataSource} from '../datasources'; + +export interface GhQueryService { + // this is where you define the Node.js methods that will be + // mapped to REST/SOAP/gRPC operations as stated in the datasource + // json file. + + // Add the three methods here. + // Make sure the function names and the parameter names matches + // the ones you defined in the datasource + getIssuesByLabel(repo: string, label: string): Promise<QueryResponse>; + getIssuesByURL(url: string): Promise<QueryResponse>; + getIssuesWithQueryString(repo:string, querystring: string): Promise<QueryResponse>; +} + +export interface QueryResponse { + headers: any; + body: QueryResponseBody; +} +export interface QueryResponseBody { + total_count: number; + items: IssueInfo[]; +} + +export class IssueInfo { + title: string; + html_url: string; + state: string; + created_at: string; +} + + +export class GhQueryServiceProvider implements Provider<GhQueryService> { + constructor( + // githubds must match the name property in the datasource json file + @inject('datasources.githubds') + protected dataSource: GithubdsDataSource = new GithubdsDataSource(), + ) {} + + value(): Promise<GhQueryService> { + return getService(this.dataSource); + } +} diff --git a/PROD_STAND/POSTGRES/testnode/src/services/index.ts b/PROD_STAND/POSTGRES/testnode/src/services/index.ts new file mode 100644 index 0000000..52b6336 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/src/services/index.ts @@ -0,0 +1 @@ +export * from './gh-query-service.service'; diff --git a/PROD_STAND/POSTGRES/testnode/tsconfig.json b/PROD_STAND/POSTGRES/testnode/tsconfig.json new file mode 100644 index 0000000..c7b8e49 --- /dev/null +++ b/PROD_STAND/POSTGRES/testnode/tsconfig.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/PROD_STAND/RABBIT/main.tf b/PROD_STAND/RABBIT/main.tf new file mode 100644 index 0000000..dc450fc --- /dev/null +++ b/PROD_STAND/RABBIT/main.tf @@ -0,0 +1,22 @@ +// Фиксируем провайдер и версию, чтобы поведение было стабильным. +terraform { + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "2.1.10" + } + } +} + +// Провайдер Nubes для создания ресурсов. +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://deck-api.ngcloud.ru/api/v1/index.cfm" +} + +// Токен доступа к Nubes API. +variable "api_token" { + type = string + sensitive = true + description = "Nubes API token" +} diff --git a/PROD_STAND/RABBIT/rabbit-lsd/Application.cfc b/PROD_STAND/RABBIT/rabbit-lsd/Application.cfc new file mode 100644 index 0000000..5639a44 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-lsd/Application.cfc @@ -0,0 +1,209 @@ +<cfcomponent displayname="Application" output="true"> + <!--- Базовая конфигурация приложения и подключение JAR-клиента RabbitMQ. ---> + <cfset this.Name = "rabbit-lsd-ui" /> + <cfset this.sessionmanagement = "Yes" /> + <cfset this.datasource = "testds" /> + <cfset this.javaSettings = { loadPaths = [ expandPath("./lib") ] } /> + <!--- Инициализируем datasource из переменных окружения. ---> + <cfset getDS(this.datasource) /> + + <!--- Безопасное чтение переменной окружения с дефолтом. ---> + <cffunction name="getEnv" access="private" returntype="string"> + <cfargument name="key" type="string" required="true" /> + <cfargument name="def" type="string" required="true" /> + <cfset var system = createObject("java", "java.lang.System") /> + <cfset var val = system.getEnv(arguments.key) /> + <cfif isDefined("val") AND len(val)><cfreturn val /></cfif> + <cfreturn arguments.def /> + </cffunction> + + <!--- Собираем datasource из набора *_field переменных окружения. ---> + <cffunction name="getDS" access="private" returntype="void"> + <cfargument name="dsname" type="string" required="true" /> + <cfset var system = createObject("java", "java.lang.System") /> + <cfset var ds = {} /> + <cfloop list="class,connectionString,database,driver,host,port,type,url,username,password,bundleName,bundleVersion,connectionLimit,liveTimeout,validate" item="field"> + <cfset var envVal = system.getEnv("#arguments.dsname#_#field#") /> + <cfif isDefined("envVal") AND len(envVal)><cfset ds[field] = envVal /></cfif> + </cfloop> + <cfset this.datasources[arguments.dsname] = ds /> + </cffunction> + + <!--- Публикация CRUD-сообщения в RabbitMQ. ---> + <cffunction name="publishToRabbit" access="private" returntype="void"> + <cfargument name="payload" type="struct" required="true" /> + <cfset var host = getEnv("RABBIT_HOST", "") /> + <cfset var user = getEnv("RABBIT_USER", "") /> + <cfset var pass = getEnv("RABBIT_PASSWORD", "") /> + <cfset var vhost = getEnv("RABBIT_VHOST", "/") /> + <cfset var port = getEnv("RABBIT_PORT", "5672") /> + <cfset var queueList = listToArray(getEnv("RABBIT_QUEUES", "crud_queue")) /> + <cfset var queueName = trim(queueList[1]) /> + <cfset var durable = lcase(getEnv("RABBIT_DURABLE", "true")) EQ "true" /> + <cfset var factory = createObject("java", "com.rabbitmq.client.ConnectionFactory") /> + <cfset var connection = "" /> + <cfset var channel = "" /> + + <!--- Не стартуем публикацию без базовых кредов. ---> + <cfif host EQ "" OR user EQ "" OR pass EQ ""> + <cfthrow message="Rabbit credentials are missing" /> + </cfif> + + <cfset factory.setHost(host) /> + <cfset factory.setPort(JavaCast("int", port)) /> + <cfset factory.setUsername(user) /> + <cfset factory.setPassword(pass) /> + <cfset factory.setVirtualHost(vhost) /> + + <!--- Подключаемся, объявляем очередь и отправляем JSON. ---> + <cftry> + <cfset connection = factory.newConnection() /> + <cfset channel = connection.createChannel() /> + <cfset channel.queueDeclare(queueName, durable, false, false, JavaCast("null", "")) /> + <cfset var payloadJson = serializeJSON(arguments.payload) /> + <cfset var body = createObject("java", "java.lang.String").init(payloadJson).getBytes("UTF-8") /> + <cfset channel.basicPublish("", queueName, JavaCast("null", ""), body) /> + <cfif isDefined("channel")><cfset channel.close() /></cfif> + <cfif isDefined("connection")><cfset connection.close() /></cfif> + <cfcatch> + <cfif isDefined("channel")><cfset channel.close() /></cfif> + <cfif isDefined("connection")><cfset connection.close() /></cfif> + <cfthrow message="#cfcatch.message#" /> + </cfcatch> + </cftry> + </cffunction> + + <!--- Основной обработчик запроса: подготовка БД, логов и POST-операций. ---> + <cffunction name="OnRequest" access="public" returntype="void" output="true"> + <cfargument name="template" type="string" required="true" /> + <cfset request.DS = this.datasource /> + <cfset request.tableName = getEnv("PG_TABLE", "rabbit_messages") /> + <cfset request.logTableName = getEnv("UI_LOG_TABLE", "rabbit_ui_log") /> + <cfset request.rabbitAdminUrl = getEnv("RABBIT_ADMIN_URL", "") /> + <cfset request.rabbitUser = getEnv("RABBIT_USER", "") /> + <cfset request.rabbitPassword = getEnv("RABBIT_PASSWORD", "") /> + <cfset request.nodeworkerUrl = getEnv("NODEWORKER_URL", "") /> + + <!--- Гарантируем наличие основной таблицы сообщений. ---> + <cftry> + <cfquery datasource="#request.DS#"> + CREATE TABLE IF NOT EXISTS #request.tableName# (id SERIAL PRIMARY KEY, test_data TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP) + </cfquery> + <cfcatch><cfset request.db_error = cfcatch.message /></cfcatch> + </cftry> + + <!--- Гарантируем наличие таблицы лога UI и обновляем статусы. ---> + <cftry> + <cfquery datasource="#request.DS#"> + CREATE TABLE IF NOT EXISTS #request.logTableName# ( + id SERIAL PRIMARY KEY, + action TEXT NOT NULL, + text TEXT, + request_id TEXT, + target_id INTEGER, + queued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + processed_at TIMESTAMP, + status TEXT DEFAULT 'queued', + error_reason TEXT + ) + </cfquery> + <cfquery datasource="#request.DS#"> + ALTER TABLE #request.logTableName# ADD COLUMN IF NOT EXISTS error_reason TEXT + </cfquery> + <cfquery datasource="#request.DS#"> + UPDATE #request.logTableName# + SET processed_at = NOW(), status = 'done' + WHERE status = 'queued' + AND action IN ('create','update') + AND request_id IS NOT NULL + AND EXISTS ( + SELECT 1 FROM #request.tableName# + WHERE test_data LIKE '%' || '[req:' || request_id || ']%' + ) + </cfquery> + <cfquery datasource="#request.DS#"> + UPDATE #request.logTableName# + SET processed_at = NOW(), status = 'done' + WHERE status = 'queued' + AND action = 'delete' + AND target_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM #request.tableName# WHERE id = target_id + ) + </cfquery> + <cfquery datasource="#request.DS#"> + UPDATE #request.logTableName# + SET status = 'failed', error_reason = 'target not found' + WHERE status = 'queued' + AND action = 'update' + AND target_id IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM #request.tableName# WHERE id = target_id + ) + </cfquery> + <cfcatch><cfset request.db_error = cfcatch.message /></cfcatch> + </cftry> + + <!--- Обрабатываем POST CRUD и отправляем событие в очередь. ---> + <cfif CGI.REQUEST_METHOD EQ "POST" AND structKeyExists(form, "crud_action")> + <cfset var action = lcase(form.crud_action) /> + <cfset var requestId = createUUID() /> + <cfset var payload = { "request_id" = requestId } /> + <cfset var logText = "" /> + <cfset var logTargetId = "" /> + + <!--- Формируем payload и лог для очереди. ---> + <cftry> + <cfswitch expression="#action#"> + <cfcase value="insert"> + <cfset payload.action = "create" /> + <cfset logText = trim(form.txt_content) /> + <cfset payload.text = logText & " [req:" & requestId & "]" /> + </cfcase> + <cfcase value="update"> + <cfif NOT structKeyExists(form, "id")> + <cfthrow message="Missing id for update" /> + </cfif> + <cfset payload.action = "update" /> + <cfset payload.id = val(form.id) /> + <cfset logText = trim(form.txt_content) /> + <cfset logTargetId = toString(val(form.id)) /> + <cfset payload.text = logText & " [req:" & requestId & "]" /> + </cfcase> + <cfcase value="delete"> + <cfif NOT structKeyExists(form, "id")> + <cfthrow message="Missing id for delete" /> + </cfif> + <cfset payload.action = "delete" /> + <cfset payload.id = val(form.id) /> + <cfset logTargetId = toString(val(form.id)) /> + <cfset logText = "delete id " & logTargetId /> + </cfcase> + <cfdefaultcase> + <cfthrow message="Unknown action" /> + </cfdefaultcase> + </cfswitch> + + <!--- Публикуем сообщение и пишем запись в лог UI. ---> + <cfset publishToRabbit(payload) /> + <cfquery datasource="#request.DS#"> + INSERT INTO #request.logTableName# (action, text, request_id, target_id, queued_at, status) + VALUES ( + <cfqueryparam value="#payload.action#" cfsqltype="cf_sql_varchar">, + <cfqueryparam value="#logText#" cfsqltype="cf_sql_varchar">, + <cfqueryparam value="#requestId#" cfsqltype="cf_sql_varchar">, + <cfqueryparam value="#logTargetId#" cfsqltype="cf_sql_integer" null="#NOT len(logTargetId)#">, + NOW(), + 'queued' + ) + </cfquery> + <cflocation url="#CGI.SCRIPT_NAME#" addtoken="false"> + <cfcatch> + <cfset request.queue_error = cfcatch.message /> + </cfcatch> + </cftry> + </cfif> + + <cfinclude template="#arguments.template#" /> + </cffunction> +</cfcomponent> diff --git a/PROD_STAND/RABBIT/rabbit-lsd/README.md b/PROD_STAND/RABBIT/rabbit-lsd/README.md new file mode 100644 index 0000000..cf8802f --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-lsd/README.md @@ -0,0 +1,45 @@ +# Lucee UI for Rabbit CRUD + +UI publishes CRUD actions to RabbitMQ and reads results from Postgres. + +Коротко: интерфейс пишет в RabbitMQ, а фактическое состояние берет из Postgres, чтобы показать разницу между "поставлено в очередь" и "исполнено". + +## Behavior + +- Insert/Update/Delete actions are queued via RabbitMQ. +- A status banner shows when the action was queued and when it appears in Postgres. +- The UI reads the latest rows from the table defined by `PG_TABLE`. + +## Environment variables + +Lucee datasource (required): +- `testds_connectionString` +- `testds_username` +- `testds_password` +- `testds_class` (org.postgresql.Driver) +- `testds_bundleName` (org.postgresql.jdbc) +- `testds_bundleVersion` (42.6.0) +- `testds_connectionLimit` (5) +- `testds_liveTimeout` (15) +- `testds_validate` (false) + +RabbitMQ (required): +- `RABBIT_HOST` +- `RABBIT_PORT` (default 5672) +- `RABBIT_USER` +- `RABBIT_PASSWORD` +- `RABBIT_VHOST` (default `/`) +- `RABBIT_QUEUES` (default `crud_queue`) +- `RABBIT_DURABLE` (default `true`) + +Optional: +- `PG_TABLE` (default `rabbit_messages`) + +Optional: +- `UI_LOG_TABLE` (default `rabbit_ui_log`) — таблица лога для статуса запросов UI. + +## Notes + +This app uses RabbitMQ Java client JARs located in `lib/`. + +Комментарий: JAR-ы подключены через `this.javaSettings.loadPaths`, поэтому важно держать `lib/` рядом с приложением. diff --git a/PROD_STAND/RABBIT/rabbit-lsd/index.cfm b/PROD_STAND/RABBIT/rabbit-lsd/index.cfm new file mode 100644 index 0000000..a865783 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-lsd/index.cfm @@ -0,0 +1,2 @@ +<!--- Простой редирект на основной экран UI. ---> +<cflocation addtoken="No" url="query.cfm##q"/> diff --git a/PROD_STAND/RABBIT/rabbit-lsd/lib/amqp-client-5.21.0.jar b/PROD_STAND/RABBIT/rabbit-lsd/lib/amqp-client-5.21.0.jar new file mode 100644 index 0000000..129b03b Binary files /dev/null and b/PROD_STAND/RABBIT/rabbit-lsd/lib/amqp-client-5.21.0.jar differ diff --git a/PROD_STAND/RABBIT/rabbit-lsd/lib/slf4j-api-1.7.36.jar b/PROD_STAND/RABBIT/rabbit-lsd/lib/slf4j-api-1.7.36.jar new file mode 100644 index 0000000..7d3ce68 Binary files /dev/null and b/PROD_STAND/RABBIT/rabbit-lsd/lib/slf4j-api-1.7.36.jar differ diff --git a/PROD_STAND/RABBIT/rabbit-lsd/query.cfm b/PROD_STAND/RABBIT/rabbit-lsd/query.cfm new file mode 100644 index 0000000..57d1afe --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-lsd/query.cfm @@ -0,0 +1,219 @@ +<!DOCTYPE html> +<html lang="ru"> +<head> + <meta charset="UTF-8"> + <title>Nubes | Rabbit CRUD UI + + + + + + + + + + + + + + SELECT id, action, text, request_id, target_id, queued_at, processed_at, status, error_reason + FROM #request.logTableName# + ORDER BY id DESC + + + + + + + + + + + + + + + + +
+
+ +
Lucee + Rabbit + Postgres Demo
+
+
+
+ +
+
+
+
RabbitMQ UI
+ +
Логин / Пароль
+
#rabbitUser# / #rabbitPassword#
+
+
+
NodeJS Worker
+ +
+
+
+ + +
+ +
Ошибка базы данных: #request.db_error#
+
+ +
+ #status.message# +
+
+ +
+ + + +
+
💾 — сохранить (update), 🗑 — удалить
+
+ + +
+
+

Очередь (лог отправки)

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
IDДействиеТекстTargetQueuedProcessedСтатусПричина
#id##uCase(action)##HTMLEditFormat(text)##target_id#-#dateTimeFormat(queued_at, "yyyy-mm-dd HH:nn:ss")##dateTimeFormat(processed_at, "yyyy-mm-dd HH:nn:ss")#- + #status# + #HTMLEditFormat(error_reason)#-
+
+
+ +
+

База (фактические записи)

+ + SELECT * FROM #request.tableName# ORDER BY id DESC +
+ + + + + + + + + + + + + + + + + +
IDСодержимоеRequest IDДействия
#id# +
+ + +
+
#reqId#- + +
+ + +
+
+
+
+
+
+ + diff --git a/PROD_STAND/RABBIT/rabbit-nodeworker/.gitignore b/PROD_STAND/RABBIT/rabbit-nodeworker/.gitignore new file mode 100644 index 0000000..5773660 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-nodeworker/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.npmrc +.env diff --git a/PROD_STAND/RABBIT/rabbit-nodeworker/README.md b/PROD_STAND/RABBIT/rabbit-nodeworker/README.md new file mode 100644 index 0000000..6a4238f --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-nodeworker/README.md @@ -0,0 +1,26 @@ +# Rabbit NodeJS Worker + +Consumes CRUD messages from RabbitMQ and writes to Postgres. + +Комментарий: воркер специально пишет в таблицу демо и не хранит собственное состояние, кроме метрик в памяти. + +## Environment variables + +RabbitMQ: +- `AMQP_URL` (preferred), example: `amqp://user:pass@host:5672/vhost` +- or `RABBIT_HOST`, `RABBIT_PORT` (default 5672), `RABBIT_USER`, `RABBIT_PASSWORD`, `RABBIT_VHOST` (default `/`) +- `RABBIT_QUEUES` (default `crud_queue`) +- `RABBIT_DURABLE` (default `true`) +- `RABBIT_PREFETCH` (default `1`) +- `REQUEUE_ON_ERROR` (default `true`) + +Postgres: +- `DATABASE_URL` (optional) +- or `PGHOST`, `PGPORT` (default 5432), `PGUSER`, `PGPASSWORD`, `PGDATABASE` (default `postgres`), `PGSSLMODE` (default `require`) +- `PG_TABLE` (default `rabbit_messages`) + +## Notes + +Health endpoint: `/healthz`. + +HTML-страница со статусом доступна на `/`. diff --git a/PROD_STAND/RABBIT/rabbit-nodeworker/package.json b/PROD_STAND/RABBIT/rabbit-nodeworker/package.json new file mode 100644 index 0000000..3413215 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-nodeworker/package.json @@ -0,0 +1,13 @@ +{ + "name": "rabbit-nodeworker", + "version": "0.1.0", + "description": "RabbitMQ worker for CRUD demo", + "main": "server.js", + "scripts": { + "start": "node server.js" + }, + "dependencies": { + "amqplib": "^0.10.4", + "pg": "^8.12.0" + } +} diff --git a/PROD_STAND/RABBIT/rabbit-nodeworker/server.js b/PROD_STAND/RABBIT/rabbit-nodeworker/server.js new file mode 100644 index 0000000..e92e1aa --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-nodeworker/server.js @@ -0,0 +1,264 @@ +const http = require("http"); +const amqp = require("amqplib"); +const { Pool } = require("pg"); + +// Состояние процесса для /healthz и статуса в UI. +const status = { + state: "starting", + error: "", + lastSuccess: "", +}; + +// Метрики обработки сообщений (показываются на HTML-странице). +const metrics = { + processed: 0, + failed: 0, + lastError: "", + lastMessageAt: "", + lastAction: "", +}; + +// Унифицированное обновление состояния. +function setStatus(state, error) { + status.state = state; + status.error = error || ""; + if (!error) { + status.lastSuccess = new Date().toISOString(); + } +} + +// HTML-страница со статусом и счетчиками. +function renderStatusPage() { + const queueName = (process.env.RABBIT_QUEUES || "crud_queue").split(",")[0] || "crud_queue"; + const tableName = process.env.PG_TABLE || "rabbit_messages"; + return ` + + + + Rabbit NodeJS Worker + + + +
+
+

NodeJS Worker

+
Queue: ${queueName} | Table: ${tableName}
+
+ ${status.state} +
+
+
+
Processed
+
${metrics.processed}
+
+
+
Failed
+
${metrics.failed}
+
+
+
Last Action
+
${metrics.lastAction || "-"}
+
${metrics.lastMessageAt || ""}
+
+
+
Last Error
+
${metrics.lastError || "-"}
+
${status.lastSuccess || ""}
+
+
+
+
+ +`; +} + +// Подготовка конфигурации PG (DATABASE_URL имеет приоритет). +function buildPgConfig() { + if (process.env.DATABASE_URL) { + return { + connectionString: process.env.DATABASE_URL, + ssl: process.env.PGSSLMODE === "require" ? { rejectUnauthorized: false } : undefined, + }; + } + + return { + host: process.env.PGHOST, + port: Number(process.env.PGPORT || "5432"), + user: process.env.PGUSER, + password: process.env.PGPASSWORD, + database: process.env.PGDATABASE || "postgres", + ssl: process.env.PGSSLMODE === "require" ? { rejectUnauthorized: false } : undefined, + }; +} + +// Собираем AMQP URL из переменных окружения. +function buildAmqpUrl() { + if (process.env.AMQP_URL) { + return process.env.AMQP_URL; + } + const host = process.env.RABBIT_HOST || ""; + const port = process.env.RABBIT_PORT || "5672"; + const user = process.env.RABBIT_USER || ""; + const pass = process.env.RABBIT_PASSWORD || ""; + const vhost = process.env.RABBIT_VHOST || "/"; + if (!host || !user || !pass) { + throw new Error("Missing RABBIT_HOST/RABBIT_USER/RABBIT_PASSWORD or AMQP_URL"); + } + return `amqp://${user}:${pass}@${host}:${port}${vhost}`; +} + +const pool = new Pool(buildPgConfig()); + +// Минимальная таблица для демо, если БД пустая. +async function ensureTable(table) { + await pool.query( + `CREATE TABLE IF NOT EXISTS ${table} (id SERIAL PRIMARY KEY, test_data TEXT, created_at TIMESTAMP DEFAULT NOW())` + ); +} + +// Парсим JSON, нормализуем ключи, иначе считаем текстом. +function parseMessage(buffer) { + const body = buffer.toString("utf8"); + try { + const parsed = JSON.parse(body); + const normalized = Object.keys(parsed || {}).reduce((acc, key) => { + acc[key.toLowerCase()] = parsed[key]; + return acc; + }, {}); + return { + action: normalized.action || "create", + id: normalized.id, + text: normalized.text, + requestId: normalized.request_id || normalized.requestid, + }; + } catch { + return { + action: "create", + text: body.trim() || "(empty)", + }; + } +} + +// CRUD-операции по сообщению. +async function handleMessage(table, msg) { + const action = (msg.action || "create").toLowerCase(); + if (action === "create") { + const text = msg.text || "(empty)"; + await pool.query(`INSERT INTO ${table} (test_data) VALUES ($1)`, [text]); + return; + } + if (action === "update") { + if (!msg.id) { + throw new Error("missing id for update"); + } + await pool.query(`UPDATE ${table} SET test_data=$1 WHERE id=$2`, [msg.text || "", msg.id]); + return; + } + if (action === "delete") { + if (!msg.id) { + throw new Error("missing id for delete"); + } + await pool.query(`DELETE FROM ${table} WHERE id=$1`, [msg.id]); + return; + } + throw new Error("invalid action"); +} + +// Небольшой HTTP-сервер для /healthz и HTML-статуса. +function startHttpServer() { + const port = process.env.PORT || 3000; + const server = http.createServer((req, res) => { + if (req.url === "/healthz") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(JSON.stringify(status)); + return; + } + res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" }); + res.end(renderStatusPage()); + }); + server.listen(port, () => { + console.log(`health server listening on ${port}`); + }); +} + +// Основной цикл: подключение к RabbitMQ и обработка сообщений. +async function consumeLoop() { + const table = process.env.PG_TABLE || "rabbit_messages"; + const queueName = (process.env.RABBIT_QUEUES || "crud_queue") + .split(",") + .map((q) => q.trim()) + .filter(Boolean)[0] || "crud_queue"; + const durable = String(process.env.RABBIT_DURABLE || "true").toLowerCase() === "true"; + const prefetch = Number(process.env.RABBIT_PREFETCH || "1"); + const requeueOnError = String(process.env.REQUEUE_ON_ERROR || "true").toLowerCase() === "true"; + + // Гарантируем наличие таблицы перед стартом консьюмера. + await ensureTable(table); + + let backoffMs = 2000; + while (true) { + try { + const amqpUrl = buildAmqpUrl(); + const connection = await amqp.connect(amqpUrl); + const channel = await connection.createChannel(); + await channel.prefetch(prefetch); + await channel.assertQueue(queueName, { durable }); + + setStatus("running", ""); + + // Обрабатываем сообщения и фиксируем успех/ошибки. + channel.consume(queueName, async (msg) => { + if (!msg) { + return; + } + try { + const parsed = parseMessage(msg.content); + await handleMessage(table, parsed); + metrics.processed += 1; + metrics.lastAction = parsed.action || "create"; + metrics.lastMessageAt = new Date().toISOString(); + channel.ack(msg); + } catch (err) { + metrics.failed += 1; + metrics.lastError = String(err.message || err); + metrics.lastMessageAt = new Date().toISOString(); + if (String(err.message || "").includes("missing id") || String(err.message || "").includes("invalid action")) { + channel.nack(msg, false, false); + return; + } + channel.nack(msg, false, requeueOnError); + } + }); + + await new Promise((resolve, reject) => { + connection.on("close", resolve); + connection.on("error", reject); + }); + } catch (err) { + setStatus("degraded", String(err.message || err)); + await new Promise((r) => setTimeout(r, backoffMs)); + if (backoffMs < 30000) { + backoffMs *= 2; + } + continue; + } + } +} + +startHttpServer(); +consumeLoop().catch((err) => { + setStatus("failed", String(err.message || err)); + console.error(err); +}); diff --git a/PROD_STAND/RABBIT/rabbit-worker/.gitignore b/PROD_STAND/RABBIT/rabbit-worker/.gitignore new file mode 100644 index 0000000..1dda6e5 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-worker/.gitignore @@ -0,0 +1,3 @@ +bin/ +*.log +.env diff --git a/PROD_STAND/RABBIT/rabbit-worker/Dockerfile b/PROD_STAND/RABBIT/rabbit-worker/Dockerfile new file mode 100644 index 0000000..34eda5f --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-worker/Dockerfile @@ -0,0 +1,23 @@ +# Сборочный этап: собираем статический бинарник. +FROM golang:1.22-alpine AS build + +WORKDIR /src + +COPY go.mod ./ +RUN go mod download + +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /out/worker ./ + +# Рантайм-этап: минимальный образ с ca-certificates. +FROM alpine:3.20 + +RUN apk add --no-cache ca-certificates + +WORKDIR /app +COPY --from=build /out/worker /app/worker + +EXPOSE 8080 + +CMD ["/app/worker"] diff --git a/PROD_STAND/RABBIT/rabbit-worker/README.md b/PROD_STAND/RABBIT/rabbit-worker/README.md new file mode 100644 index 0000000..4cd1039 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-worker/README.md @@ -0,0 +1,44 @@ +# Rabbit Worker (Go) + +Consumes CRUD messages from RabbitMQ and writes to Postgres. + +Комментарий: HTTP-эндпоинт нужен только для health-check, остальная логика работает через RabbitMQ. + +## Environment variables + +RabbitMQ: +- `AMQP_URL` (preferred), example: `amqp://user:pass@host:5672/vhost` +- or `RABBIT_HOST`, `RABBIT_PORT` (default 5672), `RABBIT_USER`, `RABBIT_PASSWORD`, `RABBIT_VHOST` (default `/`) +- `RABBIT_QUEUES` (comma-separated, default `crud_queue`) +- `RABBIT_DURABLE` (default `true`) +- `RABBIT_PREFETCH` (default `1`) +- `REQUEUE_ON_ERROR` (default `true`) + +Postgres: +- `DATABASE_URL` (preferred) +- or `PGHOST`, `PGPORT` (default 5432), `PGUSER`, `PGPASSWORD`, `PGDATABASE` (default `postgres`), `PGSSLMODE` (default `require`) +- `PG_TABLE` (default `nubes_test_table`) + +## Message format + +JSON payload: +```json +{"action":"create","text":"hello","id":1} +``` + +Fields: +- `action`: `create` | `update` | `delete` +- `id`: required for `update` and `delete` +- `text`: used by `create` and `update` + +If the payload is not JSON, it is treated as `create` with body as text. + +Примечание: сообщения без `id` для update/delete считаются некорректными и отбрасываются. + +## Build + +```bash +cd PROD_STAND/RABBIT/rabbit-worker + +docker build -t /: . +``` diff --git a/PROD_STAND/RABBIT/rabbit-worker/go.mod b/PROD_STAND/RABBIT/rabbit-worker/go.mod new file mode 100644 index 0000000..4780409 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-worker/go.mod @@ -0,0 +1,17 @@ +module gitea-naeel.giteak8s.services.ngcloud.ru/naeel/rabbit-worker + +go 1.22 + +require ( + github.com/jackc/pgx/v5 v5.5.5 + github.com/rabbitmq/amqp091-go v1.10.0 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + golang.org/x/crypto v0.17.0 // indirect + golang.org/x/sync v0.1.0 // indirect + golang.org/x/text v0.14.0 // indirect +) diff --git a/PROD_STAND/RABBIT/rabbit-worker/go.sum b/PROD_STAND/RABBIT/rabbit-worker/go.sum new file mode 100644 index 0000000..242ae45 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-worker/go.sum @@ -0,0 +1,32 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rabbitmq/amqp091-go v1.10.0 h1:STpn5XsHlHGcecLmMFCtg7mqq0RnD+zFr4uzukfVhBw= +github.com/rabbitmq/amqp091-go v1.10.0/go.mod h1:Hy4jKW5kQART1u+JkDTF9YYOQUHXqMuhrgxOEeS7G4o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/PROD_STAND/RABBIT/rabbit-worker/main.go b/PROD_STAND/RABBIT/rabbit-worker/main.go new file mode 100644 index 0000000..7cefc9c --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit-worker/main.go @@ -0,0 +1,341 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "os" + "os/signal" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + amqp "github.com/rabbitmq/amqp091-go" +) + +// Воркер держит HTTP /healthz и параллельно слушает RabbitMQ, +// записывая события в Postgres. Даже при ошибках контейнер остается живым. + +type Message struct { + Action string `json:"action"` + ID *int64 `json:"id,omitempty"` + Text string `json:"text,omitempty"` + Source string `json:"source,omitempty"` + RequestID string `json:"request_id,omitempty"` +} + +type workerStatus struct { + mu sync.Mutex + state string + lastError string + lastSuccess time.Time +} + +func (s *workerStatus) set(state, err string) { + s.mu.Lock() + defer s.mu.Unlock() + s.state = state + s.lastError = err + if err == "" { + s.lastSuccess = time.Now() + } +} + +func (s *workerStatus) snapshot() map[string]string { + s.mu.Lock() + defer s.mu.Unlock() + result := map[string]string{ + "state": s.state, + "error": s.lastError, + "timestamp": s.lastSuccess.Format(time.RFC3339), + } + return result +} + +// getenv читает переменную окружения с дефолтом. +func getenv(key, def string) string { + val := os.Getenv(key) + if val == "" { + return def + } + return val +} + +// getenvInt читает int-переменную окружения с дефолтом. +func getenvInt(key string, def int) int { + val := os.Getenv(key) + if val == "" { + return def + } + parsed, err := strconv.Atoi(val) + if err != nil { + return def + } + return parsed +} + +// buildAMQPURL собирает URL для RabbitMQ. +func buildAMQPURL() (string, error) { + if url := os.Getenv("AMQP_URL"); url != "" { + return url, nil + } + + host := getenv("RABBIT_HOST", "") + port := getenv("RABBIT_PORT", "5672") + user := getenv("RABBIT_USER", "") + pass := getenv("RABBIT_PASSWORD", "") + vhost := getenv("RABBIT_VHOST", "/") + + if host == "" || user == "" || pass == "" { + return "", errors.New("missing RABBIT_HOST/RABBIT_USER/RABBIT_PASSWORD or AMQP_URL") + } + + return fmt.Sprintf("amqp://%s:%s@%s:%s%s", user, pass, host, port, vhost), nil +} + +// buildPgConnString собирает строку подключения к Postgres. +func buildPgConnString() (string, error) { + if url := os.Getenv("DATABASE_URL"); url != "" { + return url, nil + } + + host := getenv("PGHOST", "") + if host == "" { + return "", errors.New("missing PGHOST or DATABASE_URL") + } + port := getenv("PGPORT", "5432") + user := getenv("PGUSER", "") + pass := getenv("PGPASSWORD", "") + db := getenv("PGDATABASE", "postgres") + ssl := getenv("PGSSLMODE", "require") + + if user == "" || pass == "" { + return "", errors.New("missing PGUSER/PGPASSWORD or DATABASE_URL") + } + + return fmt.Sprintf( + "postgresql://%s:%s@%s:%s/%s?sslmode=%s", + user, + pass, + host, + port, + db, + ssl, + ), nil +} + +// ensureTable гарантирует наличие таблицы для демо. +func ensureTable(ctx context.Context, pool *pgxpool.Pool, table string) error { + query := fmt.Sprintf( + "CREATE TABLE IF NOT EXISTS %s (id SERIAL PRIMARY KEY, test_data TEXT, created_at TIMESTAMP DEFAULT NOW())", + table, + ) + _, err := pool.Exec(ctx, query) + return err +} + +// parseMessage принимает JSON и fallback-ит в текст. +func parseMessage(body []byte) Message { + msg := Message{Action: "create"} + if err := json.Unmarshal(body, &msg); err != nil { + msg.Text = strings.TrimSpace(string(body)) + if msg.Text == "" { + msg.Text = "(empty)" + } + return msg + } + if msg.Action == "" { + msg.Action = "create" + } + return msg +} + +var errInvalidMessage = errors.New("invalid message") + +// handleMessage выполняет CRUD над таблицей. +func handleMessage(ctx context.Context, pool *pgxpool.Pool, table string, msg Message) error { + ctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + + switch strings.ToLower(msg.Action) { + case "create": + text := msg.Text + if text == "" { + text = "(empty)" + } + query := fmt.Sprintf("INSERT INTO %s (test_data) VALUES ($1)", table) + _, err := pool.Exec(ctx, query, text) + return err + case "update": + if msg.ID == nil { + return errInvalidMessage + } + query := fmt.Sprintf("UPDATE %s SET test_data=$1 WHERE id=$2", table) + _, err := pool.Exec(ctx, query, msg.Text, *msg.ID) + return err + case "delete": + if msg.ID == nil { + return errInvalidMessage + } + query := fmt.Sprintf("DELETE FROM %s WHERE id=$1", table) + _, err := pool.Exec(ctx, query, *msg.ID) + return err + default: + return errInvalidMessage + } +} + +// startHTTPServer поднимает /healthz и базовый ответ на /. +func startHTTPServer(port string, status *workerStatus) { + http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { + payload, _ := json.Marshal(status.snapshot()) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write(payload) + }) + + http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("rabbit-worker ready")) + }) + + log.Printf("listening on :%s", port) + if err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil); err != nil { + log.Fatal(err) + } +} + +// runWorker подключается к Rabbit и обрабатывает сообщения. +func runWorker(ctx context.Context, status *workerStatus) error { + amqpURL, err := buildAMQPURL() + if err != nil { + return err + } + + pgConn, err := buildPgConnString() + if err != nil { + return err + } + + // Список очередей поддерживает несколько значений через запятую. + queues := strings.Split(getenv("RABBIT_QUEUES", "crud_queue"), ",") + for i := range queues { + queues[i] = strings.TrimSpace(queues[i]) + } + queueDurable := getenv("RABBIT_DURABLE", "true") == "true" + prefetch := getenvInt("RABBIT_PREFETCH", 1) + requeueOnError := getenv("REQUEUE_ON_ERROR", "true") == "true" + table := getenv("PG_TABLE", "nubes_test_table") + + pool, err := pgxpool.New(ctx, pgConn) + if err != nil { + return fmt.Errorf("pg pool: %w", err) + } + defer pool.Close() + + if err := pool.Ping(ctx); err != nil { + return fmt.Errorf("pg ping: %w", err) + } + if err := ensureTable(ctx, pool, table); err != nil { + return fmt.Errorf("ensure table: %w", err) + } + + conn, err := amqp.Dial(amqpURL) + if err != nil { + return fmt.Errorf("amqp dial: %w", err) + } + defer conn.Close() + + ch, err := conn.Channel() + if err != nil { + return fmt.Errorf("amqp channel: %w", err) + } + defer ch.Close() + + if err := ch.Qos(prefetch, 0, false); err != nil { + return fmt.Errorf("amqp qos: %w", err) + } + + for _, q := range queues { + if q == "" { + continue + } + if _, err := ch.QueueDeclare(q, queueDurable, false, false, false, nil); err != nil { + return fmt.Errorf("queue declare %s: %w", q, err) + } + } + + // Сливаем несколько очередей в один канал сообщений. + msgs := make(chan amqp.Delivery) + for _, q := range queues { + if q == "" { + continue + } + delivery, err := ch.Consume(q, "", false, false, false, false, nil) + if err != nil { + return fmt.Errorf("consume %s: %w", q, err) + } + + go func(d <-chan amqp.Delivery) { + for m := range d { + msgs <- m + } + }(delivery) + } + + status.set("running", "") + for { + select { + case <-ctx.Done(): + return nil + case m := <-msgs: + msg := parseMessage(m.Body) + if err := handleMessage(ctx, pool, table, msg); err != nil { + if errors.Is(err, errInvalidMessage) { + _ = m.Nack(false, false) + log.Printf("reject message: %v", err) + continue + } + _ = m.Nack(false, requeueOnError) + log.Printf("error handling message: %v", err) + continue + } + _ = m.Ack(false) + } + } +} + +func main() { + port := getenv("PORT", "8080") + status := &workerStatus{state: "starting"} + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + go startHTTPServer(port, status) + + // Экспоненциальный backoff при ошибках подключения. + backoff := 2 * time.Second + for { + if ctx.Err() != nil { + return + } + if err := runWorker(ctx, status); err != nil { + status.set("degraded", err.Error()) + log.Printf("worker error: %v", err) + time.Sleep(backoff) + if backoff < 30*time.Second { + backoff *= 2 + } + continue + } + status.set("stopped", "") + return + } +} diff --git a/PROD_STAND/RABBIT/rabbit_worker_disabled.tf b/PROD_STAND/RABBIT/rabbit_worker_disabled.tf new file mode 100644 index 0000000..a647584 --- /dev/null +++ b/PROD_STAND/RABBIT/rabbit_worker_disabled.tf @@ -0,0 +1,33 @@ +# Disabled rabbit worker resource (create-only json_env issue). +# Отключено из-за create-only json_env у nubes_http. +# +# resource "nubes_http" "rabbit_worker" { +# resource_name = "rb-worker-02" +# resource_realm = "k8s-3.ext.nubes.ru" +# domain = "rb-worker-02" +# registry_path = "naeel/rabbit-worker:0.3" +# resource_c_p_u = 100 +# resource_memory = 128 +# resource_instances = 1 +# +# json_env = jsonencode({ +# RABBIT_HOST = try(nubes_rabbitmq.rb1.state_out_flat["internalConnect.master"], nubes_rabbitmq.rb1.state_out_flat["inernalConnect.master"]) +# RABBIT_PORT = "5672" +# RABBIT_USER = nubes_rabbitmq.rb1.vault_secrets["adminUser"] +# RABBIT_PASSWORD = nubes_rabbitmq.rb1.vault_secrets["adminPass"] +# RABBIT_VHOST = "/" +# RABBIT_QUEUES = "crud_queue" +# RABBIT_DURABLE = "true" +# RABBIT_PREFETCH = "1" +# REQUEUE_ON_ERROR = "true" +# PGHOST = var.PGHOST +# PGPORT = "5432" +# PGUSER = var.PGUSER +# PGPASSWORD = var.PGPASSWORD +# PGDATABASE = "postgres" +# PGSSLMODE = "require" +# PG_TABLE = "rabbit_messages" +# }) +# +# depends_on = [nubes_rabbitmq.rb1] +# } diff --git a/PROD_STAND/RABBIT/resources.tf b/PROD_STAND/RABBIT/resources.tf new file mode 100644 index 0000000..ccde7ff --- /dev/null +++ b/PROD_STAND/RABBIT/resources.tf @@ -0,0 +1,104 @@ +# RabbitMQ кластер для демо. +resource "nubes_rabbitmq" "rb1" { + resource_name = "rabbit_0" + resource_realm = "k8s-3.ext.nubes.ru" + resource_instances = 1 + resource_memory = 512 + resource_c_p_u = 500 + resource_disk = 5 + need_external_address_master = false + need_external_address_slave = false +} + +# Lucee UI, который пишет в Rabbit и читает из Postgres. +resource "nubes_lucee" "rabbit_ui" { + resource_name = "rb-lucee-ui" + resource_realm = "k8s-3.ext.nubes.ru" + domain = "rb-ui" + app_version = "5.4" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/rabbit-lsd.git" + resource_c_p_u = 300 + resource_memory = 512 + resource_instances = 1 + + # Переменные окружения для datasource, Rabbit и UI. + json_env = jsonencode({ + testds_bundleName = "org.postgresql.jdbc" + testds_bundleVersion = "42.6.0" + testds_class = "org.postgresql.Driver" + testds_connectionLimit = "5" + testds_connectionString = "jdbc:postgresql://${var.PGHOST}:5432/postgres" + testds_liveTimeout = "15" + testds_username = var.PGUSER + testds_password = var.PGPASSWORD + testds_validate = "false" + PG_TABLE = "rabbit_messages" + UI_LOG_TABLE = "rabbit_ui_log" + RABBIT_HOST = try(nubes_rabbitmq.rb1.state_out_flat["internalConnect.master"], nubes_rabbitmq.rb1.state_out_flat["inernalConnect.master"]) + RABBIT_PORT = "5672" + RABBIT_USER = nubes_rabbitmq.rb1.vault_secrets["adminUser"] + RABBIT_PASSWORD = nubes_rabbitmq.rb1.vault_secrets["adminPass"] + RABBIT_VHOST = "/" + RABBIT_QUEUES = "crud_queue" + RABBIT_DURABLE = "true" + RABBIT_ADMIN_URL = "https://${nubes_rabbitmq.rb1.state_out_flat["externalConnect.admin.fqdn"]}/#/" + NODEWORKER_URL = "https://nodeworker.nodejsk8s.services.ngcloud.ru/" + }) + + depends_on = [nubes_rabbitmq.rb1] +} + +# NodeJS воркер, который переносит CRUD из очереди в Postgres. +resource "nubes_nodejs" "rabbit_nodeworker" { + resource_name = "nodeworker" + resource_realm = "k8s-3.ext.nubes.ru" + domain = "nodeworker" + app_version = "23" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/rabbit-nodeworker.git" + health_path = "/healthz" + resource_c_p_u = 100 + resource_memory = 256 + resource_instances = 1 + + # Параметры подключения к Rabbit и Postgres. + json_env = jsonencode({ + RABBIT_HOST = try(nubes_rabbitmq.rb1.state_out_flat["internalConnect.master"], nubes_rabbitmq.rb1.state_out_flat["inernalConnect.master"]) + RABBIT_PORT = "5672" + RABBIT_USER = nubes_rabbitmq.rb1.vault_secrets["adminUser"] + RABBIT_PASSWORD = nubes_rabbitmq.rb1.vault_secrets["adminPass"] + RABBIT_VHOST = "/" + RABBIT_QUEUES = "crud_queue" + RABBIT_DURABLE = "true" + RABBIT_PREFETCH = "1" + REQUEUE_ON_ERROR = "true" + PGHOST = var.PGHOST + PGPORT = "5432" + PGUSER = var.PGUSER + PGPASSWORD = var.PGPASSWORD + PGDATABASE = "postgres" + PGSSLMODE = "require" + PG_TABLE = "rabbit_messages" + }) + + depends_on = [nubes_rabbitmq.rb1] +} + +# # Ручной триггер redeploy для воркера. +# resource "nubes_nodejs_redeploy" "rabbit_nodeworker_redeploy" { +# nodejs_id = nubes_nodejs.rabbit_nodeworker.id +# resource_realm = "k8s-3.ext.nubes.ru" +# run_id = "redeploy-2026-02-2-01134" +# } + +# # Ручной триггер restart для Lucee UI. +# resource "nubes_lucee_restart" "rabbit_ui_restart" { +# lucee_id = nubes_lucee.rabbit_ui.id +# run_id = "restart-2026-02-23-023" +# } + +# S3 бакет baba. +resource "nubes_s3bucket" "baba_bucket" { + resource_name = "baba-buck" + s3_user_uid = "s3-111805" + bucket_name = "baba" +} diff --git a/PROD_STAND/RABBIT/variables.tf b/PROD_STAND/RABBIT/variables.tf new file mode 100644 index 0000000..9135d58 --- /dev/null +++ b/PROD_STAND/RABBIT/variables.tf @@ -0,0 +1,18 @@ +// Пользователь Postgres для воркера и UI. +// Важно: Postgres здесь внешний, уже запущенный — параметры берутся из ЛК. +variable "PGUSER" { + type = string + default = "postgres" +} +// Пароль Postgres для воркера и UI. +// Узнайте пароль в UI Личного кабинета и укажите здесь. +variable "PGPASSWORD" { + type = string + default = "TMGMCN5GYzx8gj1GiqA4rmf19HEJKgQXSUHcN17hJNnbyZmicfhU5AfFpLQyF22I" +} +// FQDN мастера Postgres (важно для DNS внутри кластера). +// Значение берется из UI Личного кабинета для уже созданного Postgres. +variable "PGHOST" { + type = string + default = "postgresqlk8s-master.a71094c1-b0ed-4bfb-a264-37b41d279567.svc.k8s-3.ext.nubes.ru" +} diff --git a/RABBIT/main.tf b/RABBIT/main.tf new file mode 100644 index 0000000..4384dfc --- /dev/null +++ b/RABBIT/main.tf @@ -0,0 +1,30 @@ +// 2026-03-19 +// main.tf — провайдер и переменные для managed RabbitMQ в облаке Nubes. +// Тест-стенд: deck-api-test.ngcloud.ru +terraform { + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "5.0.19" + } + } +} + +// Токен доступа к Nubes API. +variable "api_token" { + type = string + sensitive = true + description = "Nubes API token" +} + +// Реалм, в котором создаётся RabbitMQ (например, k8s-5.ext.nubes.ru). +variable "realm" { + type = string + sensitive = true + description = "resource_realm для nubes_rabbitmq" +} + +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" +} diff --git a/RABBIT/resources.tf b/RABBIT/resources.tf new file mode 100644 index 0000000..160df98 --- /dev/null +++ b/RABBIT/resources.tf @@ -0,0 +1,41 @@ +// 2026-03-19 +// resources.tf — managed RabbitMQ в облаке Nubes для sless и других сервисов. + +resource "nubes_rabbitmq" "rmq" { + // Общий брокер для sless event-triggers и других сервисов облака. + resource_name = "sless-rabbit" + resource_realm = var.realm + resource_instances = 1 + resource_memory = 512 + resource_c_p_u = 500 + resource_disk = 5 + need_external_address_master = false + need_external_address_slave = false +} + +locals { + // try() — обходим опечатку в API: поле называется как internalConnect, так и inernalConnect. + rmq_host = try(nubes_rabbitmq.rmq.state_out_flat["internalConnect.master"], nubes_rabbitmq.rmq.state_out_flat["inernalConnect.master"]) + rmq_user = nubes_rabbitmq.rmq.vault_secrets["adminUser"] + rmq_password = nubes_rabbitmq.rmq.vault_secrets["adminPass"] +} + +output "rabbitmq_host" { + value = local.rmq_host +} + +output "rabbitmq_user" { + value = local.rmq_user + sensitive = true +} + +output "rabbitmq_password" { + value = local.rmq_password + sensitive = true +} + +output "rabbitmq_amqp_url" { + // Готовый URL для передачи в оператор sless (env RABBITMQ_URL). + value = "amqp://${local.rmq_user}:${local.rmq_password}@${local.rmq_host}:5672/" + sensitive = true +} diff --git a/TEST_STAND/LUCEE/main.tf b/TEST_STAND/LUCEE/main.tf new file mode 100644 index 0000000..b3ea881 --- /dev/null +++ b/TEST_STAND/LUCEE/main.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "2.0.6" + } + } +} + +variable "api_token" { + type = string + sensitive = true + description = "Nubes API token" +} + +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" +} + diff --git a/TEST_STAND/LUCEE/resources.tf b/TEST_STAND/LUCEE/resources.tf new file mode 100644 index 0000000..ebca5a2 --- /dev/null +++ b/TEST_STAND/LUCEE/resources.tf @@ -0,0 +1,95 @@ +resource "nubes_postgres" "db2" { + resource_name = "pg-tst0" + s3_uid = "s01325" + # s3_uid = "s3-111805" + resource_realm = "k8s-4-sandbox-nubes-ru" + # resource_realm = "k8s-3.ext.nubes.ru" + resource_instances = 1 + resource_memory = 512 + resource_c_p_u = 500 + resource_disk = "1" + app_version = "17" + json_parameters = jsonencode({ + log_connections = "off" + log_disconnections = "off" + }) + enable_pg_pooler_master = false + enable_pg_pooler_slave = false + allow_no_s_s_l = false + auto_scale = false + auto_scale_percentage = 10 + auto_scale_tech_window = 0 + auto_scale_quota_gb = "1" + need_external_address_master = false +} + +resource "nubes_lucee" "app1" { + # Lucee-приложение, зависит от Postgres + resource_name = "lucy_0" + # resource_realm = "k8s-3.ext.nubes.ru" + resource_realm = "k8s-4-sandbox-nubes-ru" + domain = "web03" + + # git_path = "https://gitea.services.ngcloud.ru/naeel/testlucee-mirror" + # git_path = "https://gitea.services.ngcloud.ru/smishchuk/testlucee" + # ⬆️ даёт ошибку, с том числе и при обращении к API by CURL + # Приложение не запустилось. Производится полный откат установки. + # Ошибка: jlib.k8s [correctReplicaActive] | ERROR | Под(ы) не работают: 'luceek8s' (Deployment). + + # ⬇️ клон "https://gitea.services.ngcloud.ru/smishchuk/testlucee.git" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testlucee" + + # ⬇️ OK + # git_path = "https://github.com/xahys/testlucee" + +json_env = jsonencode({ + # 🔗 Настройки Data Source 'testds' для Lucee (Application.cfc) + testds_class = "org.postgresql.Driver" # 📂 Драйвер БД + testds_bundleName = "org.postgresql.jdbc" # 📦 Имя бандла JDBC + testds_bundleVersion = "42.6.0" # 🔢 Версия драйвера + testds_connectionString = "jdbc:postgresql://${nubes_postgres.db2.state_out_flat["internalConnect.master"]}:5432/postgres" # 🚀 Строка подключения + testds_username = nubes_postgres.db2.vault_secrets["adminUser"]# 👤 Логин + testds_password = nubes_postgres.db2.vault_secrets["adminPass"] # 🔑 Пароль + testds_connectionLimit = "5" # 🚦 Лимит соединений + testds_liveTimeout = "15" # ⏳ Таймаут жизни + testds_validate = "false" # ✅ Валидация при запросе + }) + + resource_c_p_u = 300 + resource_memory = 512 + resource_instances = 1 + app_version = "5.4" + + depends_on = [nubes_postgres.db2] +} + +resource "nubes_nodejs" "app2" { + # NodeJS-приложение, использует тот же Postgres + resource_name = "node_0" + # resource_realm = "k8s-3.ext.nubes.ru" + resource_realm = "k8s-4-sandbox-nubes-ru" + domain = "node" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testnode" + health_path = "/healthz" + app_version = "23" + + json_env = jsonencode({ + PGHOST = nubes_postgres.db2.state_out_flat["internalConnect.master"] + PGPORT = "5432" + PGUSER = nubes_postgres.db2.vault_secrets["adminUser"] + PGPASSWORD = nubes_postgres.db2.vault_secrets["adminPass"] + PGSSLMODE = "require" + DATABASE_URL = format( + "postgresql://%s:%s@%s:5432/postgres", + nubes_postgres.db2.vault_secrets["adminUser"], + nubes_postgres.db2.vault_secrets["adminPass"], + nubes_postgres.db2.state_out_flat["internalConnect.master"] + ) + }) + + resource_c_p_u = 300 + resource_memory = 256 + resource_instances = 1 + + depends_on = [nubes_postgres.db2] +} diff --git a/TEST_STAND/LUCEE/terraform.tfvars.example b/TEST_STAND/LUCEE/terraform.tfvars.example new file mode 100644 index 0000000..9881877 --- /dev/null +++ b/TEST_STAND/LUCEE/terraform.tfvars.example @@ -0,0 +1 @@ +api_token = "" diff --git a/TEST_STAND/MARIA_DB/main.tf b/TEST_STAND/MARIA_DB/main.tf new file mode 100644 index 0000000..9417488 --- /dev/null +++ b/TEST_STAND/MARIA_DB/main.tf @@ -0,0 +1,20 @@ +terraform { + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "2.0.8" + } + } +} + +variable "api_token" { + type = string + sensitive = true + description = "Nubes API token" +} + +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" +} + diff --git a/TEST_STAND/MARIA_DB/resources.tf b/TEST_STAND/MARIA_DB/resources.tf new file mode 100644 index 0000000..9fdae80 --- /dev/null +++ b/TEST_STAND/MARIA_DB/resources.tf @@ -0,0 +1,126 @@ + +resource "nubes_mariadb" "db2" { + resource_name = "mariadb-ops-01" + resource_realm = "k8s-4-sandbox-nubes-ru" + resource_instances = 1 + resource_memory = 1024 + resource_c_p_u = 500 + resource_disk = 1 + app_version = "9.4.0" + need_external_address_master = false + auto_scale = false + s3_uid = "s01325" +} + +resource "nubes_lucee" "app1" { + # Lucee-prilozhenie, zavisit ot MariaDB + resource_name = "lucy_0" + resource_realm = nubes_mariadb.db2.resource_realm + domain = "web03" + + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testlucee" + + json_env = jsonencode({ + # Nastroyki Data Source 'testds' dlya Lucee (Application.cfc) + testds_class = "org.mariadb.jdbc.Driver" + testds_bundleName = "org.mariadb.jdbc" + testds_bundleVersion = "3.3.2" + testds_connectionString = "jdbc:mariadb://${nubes_mariadb.db2.state_out_flat["internalConnect.master"]}:3306/postgres" + testds_username = nubes_mariadb.db2.vault_secrets["adminUser"] + testds_password = nubes_mariadb.db2.vault_secrets["adminPass"] + testds_connectionLimit = "5" + testds_liveTimeout = "15" + testds_validate = "false" + }) + + resource_c_p_u = 300 + resource_memory = 512 + resource_instances = 1 + app_version = "5.4" + + depends_on = [nubes_mariadb.db2] +} + +resource "nubes_lucee_restart" "app1_restart" { + lucee_id = nubes_lucee.app1.id + # Bump run_id to trigger another restart. + run_id = "restart-1" +} + +resource "nubes_flask" "app2" { + resource_name = "flask_0" + resource_realm = nubes_mariadb.db2.resource_realm + domain = "flask" + git_path = "https://github.com/Foxyhhd/Baldurs-Gate-test.git" + + json_env = jsonencode({ + DB_HOST = nubes_mariadb.db2.state_out_flat["internalConnect.master"] + DB_PORT = "3306" + DB_USER = nubes_mariadb.db2.vault_secrets["adminUser"] + DB_PASSWORD = nubes_mariadb.db2.vault_secrets["adminPass"] + DB_NAME = "postgres" + DATABASE_URL = format( + "mysql+pymysql://%s:%s@%s:3306/postgres", + nubes_mariadb.db2.vault_secrets["adminUser"], + nubes_mariadb.db2.vault_secrets["adminPass"], + nubes_mariadb.db2.state_out_flat["internalConnect.master"] + ) + }) + + resource_c_p_u = 300 + resource_memory = 256 + resource_instances = 1 + + depends_on = [nubes_mariadb.db2] +} + +resource "nubes_nodejs" "app3" { + resource_name = "node_0" + resource_realm = nubes_mariadb.db2.resource_realm + domain = "node" + git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testnode" + health_path = "/healthz" + app_version = "23" + + json_env = jsonencode({ + DB_HOST = nubes_mariadb.db2.state_out_flat["internalConnect.master"] + DB_PORT = "3306" + DB_USER = nubes_mariadb.db2.vault_secrets["adminUser"] + DB_PASSWORD = nubes_mariadb.db2.vault_secrets["adminPass"] + DB_NAME = "postgres" + DATABASE_URL = format( + "mysql://%s:%s@%s:3306/postgres", + nubes_mariadb.db2.vault_secrets["adminUser"], + nubes_mariadb.db2.vault_secrets["adminPass"], + nubes_mariadb.db2.state_out_flat["internalConnect.master"] + ) + }) + + resource_c_p_u = 300 + resource_memory = 256 + resource_instances = 1 + + depends_on = [nubes_mariadb.db2] +} + +resource "nubes_http" "mariadb_check" { + resource_name = "mariadb-check-01" + resource_realm = nubes_mariadb.db2.resource_realm + domain = "mariadb-check" + registry_path = "naeel/mariadb-check:0.1" + resource_c_p_u = 100 + resource_memory = 256 + resource_instances = 1 + + json_env = jsonencode({ + DB_HOST = nubes_mariadb.db2.state_out_flat["internalConnect.master"] + DB_PORT = "3306" + DB_USER = nubes_mariadb.db2.vault_secrets["adminUser"] + DB_PASSWORD = nubes_mariadb.db2.vault_secrets["adminPass"] + DB_NAME = "postgres" + }) + + depends_on = [nubes_mariadb.db2] +} + + diff --git a/TEST_STAND/MARIA_DB/terraform.tfvars.example b/TEST_STAND/MARIA_DB/terraform.tfvars.example new file mode 100644 index 0000000..9881877 --- /dev/null +++ b/TEST_STAND/MARIA_DB/terraform.tfvars.example @@ -0,0 +1 @@ +api_token = "" diff --git a/TEST_STAND/MARIA_DB/terraformrc.dev b/TEST_STAND/MARIA_DB/terraformrc.dev new file mode 100644 index 0000000..f53903b --- /dev/null +++ b/TEST_STAND/MARIA_DB/terraformrc.dev @@ -0,0 +1,6 @@ +provider_installation { + dev_overrides { + "terra.k8c.ru/nubes/nubes" = "../../universal_rebuild/bin" + } + direct {} +} diff --git a/TEST_STAND/POSTGRES/luceUNDnode.tf b/TEST_STAND/POSTGRES/luceUNDnode.tf new file mode 100644 index 0000000..033fbab --- /dev/null +++ b/TEST_STAND/POSTGRES/luceUNDnode.tf @@ -0,0 +1,71 @@ + +# resource "nubes_lucee" "app1" { +# # Lucee-приложение, зависит от Postgres +# resource_name = "lucy_teststand_0" +# resource_realm = nubes_postgres.npg.resource_realm +# domain = "web-test-stand" + +# git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testlucee.git" + +# json_env = jsonencode({ +# # 🔗 Настройки Data Source 'testds' для Lucee (Application.cfc) +# testds_class = "org.postgresql.Driver" # 📂 Драйвер БД +# testds_bundleName = "org.postgresql.jdbc" # 📦 Имя бандла JDBC +# testds_bundleVersion = "42.6.0" # 🔢 Версия драйвера +# testds_connectionString = "jdbc:postgresql://${nubes_postgres.npg.state_out_flat["internalConnect.master"]}:5432/${nubes_postgres_database.db2_app.db_name}?sslmode=require" # 🚀 Строка подключения +# testds_username = nubes_postgres_user.pg_user.username # 👤 Логин +# testds_password = jsondecode(nubes_postgres.npg.vault_secrets["users"])[nubes_postgres_user.pg_user.username]["password"] # 🔑 Пароль +# testds_connectionLimit = "5" # 🚦 Лимит соединений +# testds_liveTimeout = "15" # ⏳ Таймаут жизни +# testds_validate = "false" # ✅ Валидация при запросе +# }) + +# resource_c_p_u = 300 +# resource_memory = 512 +# resource_instances = 1 +# app_version = "5.4" + +# depends_on = [nubes_postgres.npg] +# } + +# resource "nubes_nodejs" "app3" { +# # NodeJS демо, работающий с тем же Postgres. +# resource_name = "node_01" +# resource_realm = nubes_postgres.npg.resource_realm +# domain = "node07" +# git_path = "https://gitea-naeel.giteak8s.services.ngcloud.ru/naeel/testnode.git" +# health_path = "/healthz" +# app_version = "23" + +# json_env = jsonencode({ +# # Переменные подключения к Postgres. +# PGHOST = nubes_postgres.npg.state_out_flat["internalConnect.master"] +# PGPORT = "5432" +# PGUSER = nubes_postgres_user.pg_user.username +# PGPASSWORD = jsondecode(nubes_postgres.npg.vault_secrets["users"])[nubes_postgres_user.pg_user.username]["password"] +# PGDATABASE = nubes_postgres_database.db2_app.db_name +# PGSSLMODE = "require" +# DATABASE_URL = format( +# "postgresql://%s:%s@%s:5432/%s?sslmode=require", +# nubes_postgres_user.pg_user.username, +# jsondecode(nubes_postgres.npg.vault_secrets["users"])[nubes_postgres_user.pg_user.username]["password"], +# nubes_postgres.npg.state_out_flat["internalConnect.master"], +# nubes_postgres_database.db2_app.db_name +# ) +# }) + +# resource_c_p_u = 300 +# resource_memory = 256 +# resource_instances = 1 + +# depends_on = [nubes_postgres.npg] +# } + +# output "pg_vault_secrets" { +# value = nubes_postgres.npg.vault_secrets +# sensitive = true +# } + +# terraform output -json pg_vault_secrets + + diff --git a/TEST_STAND/POSTGRES/main.tf b/TEST_STAND/POSTGRES/main.tf new file mode 100644 index 0000000..48ffc70 --- /dev/null +++ b/TEST_STAND/POSTGRES/main.tf @@ -0,0 +1,31 @@ +terraform { + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "5.0.52" + } + } +} + +variable "api_token" { + type = string + sensitive = true + description = "Nubes API token" +} +variable "s3_uid" { + type = string + sensitive = true + description = "Nubes S3 UID" +} +variable "realm" { + type = string + sensitive = true + description = "resource_realm parameter for nubes_postgres resource" +} + +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" + log_level = "debug" # none | info | debug, default = "none" +} + diff --git a/TEST_STAND/POSTGRES/resources.tf b/TEST_STAND/POSTGRES/resources.tf new file mode 100644 index 0000000..bb34f43 --- /dev/null +++ b/TEST_STAND/POSTGRES/resources.tf @@ -0,0 +1,50 @@ +resource "nubes_postgres" "npg" { + resource_name = "pg4tf033" + s3_uid = var.s3_uid + resource_realm = var.realm + resource_instances = 1 + resource_memory = 512 + resource_c_p_u = 500 + resource_disk = "1" + app_version = "17" + json_parameters = jsonencode({ + log_connections = "off" + log_disconnections = "off" + }) + enable_pg_pooler_master = false + enable_pg_pooler_slave = false + allow_no_s_s_l = false + auto_scale = false + auto_scale_percentage = 10 + auto_scale_tech_window = 0 + auto_scale_quota_gb = "1" + need_external_address_master = false + + # suspend_on_destroy = false + operation_timeout = "11m" + adopt_existing_on_create = true +} + + +# resource "nubes_s3bucket" "baba_bucket" { +# resource_name = "baba-buck-test1" +# s3_user_uid = var.s3_uid +# bucket_name = "baba000" +# adopt_existing_on_create=true +# } + +resource "nubes_postgres_user" "pg_user" { + postgres_id = nubes_postgres.npg.id + username = "user0" + role = "ddl_user" + adopt_existing_on_create = true +} + +resource "nubes_postgres_database" "db2_app" { + postgres_id = nubes_postgres.npg.id + db_name = "db_terra" + db_owner = nubes_postgres_user.pg_user.username + adopt_existing_on_create = true + # suspend_on_destroy = false +} + diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/README.md b/TEST_STAND/S3_EVENT_FUNCTION_POC/README.md new file mode 100644 index 0000000..04a3999 --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/README.md @@ -0,0 +1,45 @@ +# S3 Event Function PoC (один apply, без создания `nubes_s3`) + +Стенд делает всё в рамках одного `terraform apply`: + +1. Создаёт `nubes_s3bucket` на существующем `s3_uid`. +2. Автоматически запускает demo-check (`terraform_data + local-exec`). +3. Demo-check пишет тестовый объект в бакет. +4. Событие бакета доставляется в простую функцию, и apply печатает `CONFIRMED`. + +## Подготовка + +- Нужны: `terraform`, `python3`, `mc`. +- В `terraform.tfvars` задайте `api_token`, `s3_uid`, `bucket_name`. +- Путь к `.s3cfg_registry` можно оставить по умолчанию или задать переменной `s3cfg_path`. + +Можно взять `api_token` и `s3_uid` из `TEST_STAND/POSTGRES/terraform.tfvars`. + +## Запуск (одна команда apply) + +```bash +cd TEST_STAND/S3_EVENT_FUNCTION_POC +terraform init +terraform apply -auto-approve +``` + +## Подтверждение, что сработало + +- Во время `apply` появится строка: + - `CONFIRMED: bucket event reached function ...` +- После `apply` доступен output: + - `event_demo_confirmation` + +Проверить output: + +```bash +terraform output event_demo_confirmation +``` + +## Отладка (если failed) + +Смотрите файлы в каталоге стенда: + +- `function_stdout.log` +- `watcher_stdout.log` +- `function.log` diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/apply_confirmation.txt b/TEST_STAND/S3_EVENT_FUNCTION_POC/apply_confirmation.txt new file mode 100644 index 0000000..2f589ef --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/apply_confirmation.txt @@ -0,0 +1,13 @@ +CONFIRMED ✓ + +Source bucket : poc-s3evt-src +Dest bucket : poc-s3evt-dst +Object : event-trigger-1773587291.txt +Time : Sun Mar 15 03:08:32 PM UTC 2026 + +Что произошло: + 1. terraform создал два бакета: poc-s3evt-src и poc-s3evt-dst + 2. Скрипт написал 'event-trigger-1773587291.txt' в poc-s3evt-src + 3. Срабатывание подтверждено через: fallback check + 4. «Функция» скопировала объект в poc-s3evt-dst + 5. Копия подтверждена — тест пройден diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/main.tf b/TEST_STAND/S3_EVENT_FUNCTION_POC/main.tf new file mode 100644 index 0000000..7fa2a0d --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/main.tf @@ -0,0 +1,51 @@ +provider "nubes" { + api_token = var.api_token + api_endpoint = var.api_endpoint +} + +# Бакет-источник — сюда пишем объект +resource "nubes_s3bucket" "source" { + resource_name = "${var.bucket_name}-src" + s3_user_uid = var.s3_uid + bucket_name = "${var.bucket_name}-src" + adopt_existing_on_create = true # бакет уже создан предыдущим apply + + max_size = -1 + read_all = false + list_all = false + cors_all = false + placement = "HOT" +} + +# Бакет-назначение — сюда копирует «функция» по событию +resource "nubes_s3bucket" "dest" { + resource_name = "${var.bucket_name}-dst" + s3_user_uid = var.s3_uid + bucket_name = "${var.bucket_name}-dst" + + max_size = -1 + read_all = false + list_all = false + cors_all = false + placement = "HOT" +} + +locals { + src_bucket = try(nubes_s3bucket.source.state_params["bucketName"], "${var.bucket_name}-src") + dst_bucket = try(nubes_s3bucket.dest.state_params["bucketName"], "${var.bucket_name}-dst") +} + +# «Функция»: mc watch ловит событие в source → копирует объект в dest +resource "terraform_data" "event_delivery_check" { + depends_on = [nubes_s3bucket.source, nubes_s3bucket.dest] + + triggers_replace = { + src = local.src_bucket + dst = local.dst_bucket + s3cfg_path = var.s3cfg_path + } + + provisioner "local-exec" { + command = "bash ${path.module}/scripts/copy_on_event.sh \"${var.s3cfg_path}\" \"${local.src_bucket}\" \"${local.dst_bucket}\" \"${var.demo_timeout_sec}\"" + } +} diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/outputs.tf b/TEST_STAND/S3_EVENT_FUNCTION_POC/outputs.tf new file mode 100644 index 0000000..f372a89 --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/outputs.tf @@ -0,0 +1,11 @@ +output "source_bucket" { + value = nubes_s3bucket.source.state_params["bucketName"] +} + +output "dest_bucket" { + value = nubes_s3bucket.dest.state_params["bucketName"] +} + +output "event_demo_confirmation" { + value = try(trimspace(file("${path.module}/apply_confirmation.txt")), "confirmation file not found") +} diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/apply_and_test.sh b/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/apply_and_test.sh new file mode 100644 index 0000000..fcfc946 --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/apply_and_test.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +if [[ ! -f "$ROOT_DIR/terraform.tfvars" ]]; then + echo "ERROR: $ROOT_DIR/terraform.tfvars not found" + echo "Create it from terraform.tfvars.example or reuse values from TEST_STAND/POSTGRES/terraform.tfvars" + exit 1 +fi + +cd "$ROOT_DIR" +terraform init +terraform apply -auto-approve + +BUCKET="$(terraform output -raw bucket_name 2>/dev/null || true)" +if [[ -z "$BUCKET" ]]; then + echo "ERROR: bucket_name output is empty" + exit 1 +fi + +echo "Bucket created: $BUCKET" + +echo "Running event->function demo..." +export S3CFG="${S3CFG:-/home/naeel/remote_dev/terraform/secrets/.s3cfg_registry}" +export BUCKET +bash "$ROOT_DIR/scripts/run_demo.sh" diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/copy_on_event.sh b/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/copy_on_event.sh new file mode 100755 index 0000000..60e4bd3 --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/copy_on_event.sh @@ -0,0 +1,138 @@ +#!/usr/bin/env bash +# Симуляция event-driven функции: +# 1. mc watch следит за SOURCE бакетом (как S3 notification) +# 2. При появлении объекта — "функция" копирует его в DEST бакет +# 3. Подтверждение записывается в apply_confirmation.txt +# +# Аргументы: [timeout_sec] + +set -euo pipefail + +S3CFG="${1:?s3cfg_path required}" +SRC_BUCKET="${2:?src_bucket required}" +DST_BUCKET="${3:?dst_bucket required}" +TIMEOUT="${4:-35}" + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +POC_DIR="$(dirname "$SCRIPT_DIR")" +CONFIRM_FILE="${POC_DIR}/apply_confirmation.txt" +EVENTS_FILE="/tmp/poc_events_$$.json" +ALIAS="poc$$" + +cleanup() { + kill "$WATCHER_PID" 2>/dev/null || true + rm -f "$EVENTS_FILE" + mc alias rm "$ALIAS" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +echo "[poc] S3CFG: $S3CFG" +echo "[poc] Source bucket : $SRC_BUCKET" +echo "[poc] Dest bucket : $DST_BUCKET" +echo "[poc] Timeout : ${TIMEOUT}s" + +# Парсим .s3cfg +HOST=$(grep -E '^\s*host_base\s*=' "$S3CFG" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]') +AK=$(grep -E '^\s*access_key\s*=' "$S3CFG" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]') +SK=$(grep -E '^\s*secret_key\s*=' "$S3CFG" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]') +USE_HTTPS=$(grep -E '^\s*use_https\s*=' "$S3CFG" | head -1 | sed 's/.*=\s*//' | tr -d '[:space:]' | tr '[:upper:]' '[:lower:]') + +if [[ -z "$HOST" || -z "$AK" || -z "$SK" ]]; then + echo "[poc] ERROR: не удалось разобрать .s3cfg" >&2; exit 1 +fi + +SCHEME="https" +if [[ "$USE_HTTPS" == "false" || "$USE_HTTPS" == "0" || "$USE_HTTPS" == "no" ]]; then + SCHEME="http" +fi + +if [[ "$HOST" == http://* || "$HOST" == https://* ]]; then + ENDPOINT="$HOST" +else + ENDPOINT="${SCHEME}://${HOST}" +fi + +# Настраиваем mc alias +mc alias set "$ALIAS" "$ENDPOINT" "$AK" "$SK" --api S3v4 >/dev/null 2>&1 +echo "[poc] mc alias '$ALIAS' → ${ENDPOINT}" + +# Запускаем mc watch на SOURCE (стримит события в файл) +touch "$EVENTS_FILE" +mc watch --json "${ALIAS}/${SRC_BUCKET}" >"$EVENTS_FILE" 2>&1 & +WATCHER_PID=$! +echo "[poc] mc watch запущен (pid=$WATCHER_PID)" +sleep 2 # даём watch время подключиться + +# Имя триггерного объекта +TRIGGER_KEY="event-trigger-$(date +%s).txt" +echo "[poc] Пишем объект '${TRIGGER_KEY}' в ${SRC_BUCKET} (trigger)..." +echo "S3 event PoC trigger — $(date -u)" | mc pipe "${ALIAS}/${SRC_BUCKET}/${TRIGGER_KEY}" +echo "[poc] Объект записан. Ждём события через mc watch (макс ${TIMEOUT}s)..." + +# Ждём появления ключа в потоке событий mc watch +ELAPSED=0 +EVENT_DETECTED=false +EVENT_METHOD="" +while [[ $ELAPSED -lt $TIMEOUT ]]; do + if grep -q "${TRIGGER_KEY}" "$EVENTS_FILE" 2>/dev/null; then + EVENT_DETECTED=true + EVENT_METHOD="mc watch" + echo "[poc] Событие обнаружено в потоке mc watch (через ${ELAPSED}s)" + break + fi + sleep 2 + ELAPSED=$((ELAPSED + 2)) +done + +if [[ "$EVENT_DETECTED" != "true" ]]; then + echo "[poc] WARN: mc watch не поймал объект за ${TIMEOUT}s, проверяем напрямую..." + if mc ls "${ALIAS}/${SRC_BUCKET}/${TRIGGER_KEY}" >/dev/null 2>&1; then + EVENT_DETECTED=true + EVENT_METHOD="fallback check" + echo "[poc] Объект есть в source — выполняем копирование (fallback)" + fi +fi + +if [[ "$EVENT_DETECTED" != "true" ]]; then + echo "[poc] FAILED: объект не найден в source bucket" >&2 + cat >"$CONFIRM_FILE" </dev/null 2>&1; then + echo "[poc] FAILED: файл не найден в dest bucket" >&2 + cat >"$CONFIRM_FILE" <"$CONFIRM_FILE" < ") + sys.exit(1) + +alias = sys.argv[1] +bucket = sys.argv[2] + +cmd = ["mc", "watch", "--json", f"{alias}/{bucket}"] +proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) + +print(f"Watcher started for {alias}/{bucket}") + +for line in proc.stdout: + line = line.strip() + if not line: + continue + + try: + payload = json.loads(line) + except json.JSONDecodeError: + continue + + event = payload.get("eventName", "") + if not event: + continue + + data = json.dumps(payload).encode("utf-8") + req = urllib.request.Request( + "http://127.0.0.1:8787/invoke", + method="POST", + data=data, + headers={"Content-Type": "application/json"}, + ) + + with urllib.request.urlopen(req, timeout=5) as r: + _ = r.read() + + print(f"forwarded event: {event}") diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/run_demo.sh b/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/run_demo.sh new file mode 100644 index 0000000..b1dab96 --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/run_demo.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +if ! command -v mc >/dev/null 2>&1; then + echo "ERROR: mc is not installed" + exit 1 +fi + +if [[ -z "${S3CFG:-}" ]]; then + echo "ERROR: set S3CFG (path to .s3cfg_registry)" + exit 1 +fi + +if [[ -z "${BUCKET:-}" ]]; then + echo "ERROR: set BUCKET" + exit 1 +fi + +RUNTIME_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$RUNTIME_DIR")" + +python3 - < "$PROJECT_DIR/.mc_creds.tmp" +import configparser +cfg=configparser.RawConfigParser(); cfg.read("$S3CFG") +sec=cfg["default"] +print(sec.get("access_key","")) +print(sec.get("secret_key","")) +print(sec.get("host_base","")) +PY + +ACCESS_KEY="$(sed -n '1p' "$PROJECT_DIR/.mc_creds.tmp")" +SECRET_KEY="$(sed -n '2p' "$PROJECT_DIR/.mc_creds.tmp")" +HOST_BASE="$(sed -n '3p' "$PROJECT_DIR/.mc_creds.tmp")" +rm -f "$PROJECT_DIR/.mc_creds.tmp" + +if [[ -z "$ACCESS_KEY" || -z "$SECRET_KEY" || -z "$HOST_BASE" ]]; then + echo "ERROR: failed to read credentials from S3CFG" + exit 1 +fi + +ALIAS="poc" +mc alias rm "$ALIAS" >/dev/null 2>&1 || true +mc alias set "$ALIAS" "https://$HOST_BASE" "$ACCESS_KEY" "$SECRET_KEY" >/dev/null + +cd "$PROJECT_DIR" +rm -f function.log .function.pid .watcher.pid + +python3 "$RUNTIME_DIR/function_server.py" > function_stdout.log 2>&1 & +echo $! > .function.pid + +sleep 1 +python3 "$RUNTIME_DIR/poc_watch_and_invoke.py" "$ALIAS" "$BUCKET" > watcher_stdout.log 2>&1 & +echo $! > .watcher.pid + +sleep 2 +printf "demo-%s\n" "$(date -u +%s)" > /tmp/poc_object.txt +mc cp /tmp/poc_object.txt "$ALIAS/$BUCKET/poc/demo-$(date -u +%s).txt" >/dev/null + +sleep 3 + +echo "=== function.log ===" +if [[ -f function.log ]]; then + cat function.log +else + echo "function.log is empty" +fi + +echo "=== watcher_stdout.log (tail) ===" +tail -n 20 watcher_stdout.log || true + +echo "Done. To stop background processes:" +WATCHER_PID="$(cat .watcher.pid)" +FUNCTION_PID="$(cat .function.pid)" +echo " kill $WATCHER_PID $FUNCTION_PID" diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/single_apply_demo.sh b/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/single_apply_demo.sh new file mode 100644 index 0000000..f5eba40 --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/scripts/single_apply_demo.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 2 ]]; then + echo "usage: single_apply_demo.sh [timeout_sec]" + exit 1 +fi + +S3CFG="$1" +BUCKET="$2" +TIMEOUT_SEC="${3:-25}" + +WORKDIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ALIAS="applypoc" + +cleanup() { + set +e + if [[ -f "$WORKDIR/.watcher.pid" ]]; then kill "$(cat "$WORKDIR/.watcher.pid")" >/dev/null 2>&1 || true; fi + if [[ -f "$WORKDIR/.function.pid" ]]; then kill "$(cat "$WORKDIR/.function.pid")" >/dev/null 2>&1 || true; fi + mc alias rm "$ALIAS" >/dev/null 2>&1 || true +} +trap cleanup EXIT + +if ! command -v mc >/dev/null 2>&1; then + echo "FAILED: mc is not installed" + exit 1 +fi + +if [[ ! -f "$S3CFG" ]]; then + echo "FAILED: S3 cfg not found: $S3CFG" + exit 1 +fi + +readarray -t CREDS < <(python3 - </dev/null + +cd "$WORKDIR" +rm -f function.log function_stdout.log watcher_stdout.log .function.pid .watcher.pid apply_confirmation.txt + +python3 "$WORKDIR/scripts/function_server.py" > "$WORKDIR/function_stdout.log" 2>&1 & +echo $! > "$WORKDIR/.function.pid" + +sleep 1 +python3 "$WORKDIR/scripts/poc_watch_and_invoke.py" "$ALIAS" "$BUCKET" > "$WORKDIR/watcher_stdout.log" 2>&1 & +echo $! > "$WORKDIR/.watcher.pid" + +sleep 2 +OBJ="poc/terraform-apply-$(date -u +%s).txt" +printf "poc-%s\n" "$(date -u +%s)" > /tmp/s3_event_poc_payload.txt +mc cp /tmp/s3_event_poc_payload.txt "$ALIAS/$BUCKET/$OBJ" >/dev/null + +FOUND=0 +for _i in $(seq 1 "$TIMEOUT_SEC"); do + if [[ -s "$WORKDIR/function.log" ]] && grep -q "eventName" "$WORKDIR/function.log"; then + FOUND=1 + break + fi + sleep 1 +done + +if [[ "$FOUND" -eq 1 ]]; then + MSG="CONFIRMED: bucket event reached function for bucket=$BUCKET object=$OBJ" + echo "$MSG" + echo "$MSG" > "$WORKDIR/apply_confirmation.txt" + exit 0 +fi + +echo "FAILED: no event received by function within ${TIMEOUT_SEC}s" +echo "--- function_stdout.log ---" +tail -n 40 "$WORKDIR/function_stdout.log" || true +echo "--- watcher_stdout.log ---" +tail -n 40 "$WORKDIR/watcher_stdout.log" || true +exit 1 diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/terraform.tfvars.example b/TEST_STAND/S3_EVENT_FUNCTION_POC/terraform.tfvars.example new file mode 100644 index 0000000..634f873 --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/terraform.tfvars.example @@ -0,0 +1,7 @@ +# Используйте существующий token/s3_uid из: +# TEST_STAND/POSTGRES/terraform.tfvars + +api_token = "" +api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" +s3_uid = "332cdb0d-34bf-43bf-864d-4adcc3b556fb" +bucket_name = "poc-s3-event-demo-001" diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/variables.tf b/TEST_STAND/S3_EVENT_FUNCTION_POC/variables.tf new file mode 100644 index 0000000..690637d --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/variables.tf @@ -0,0 +1,33 @@ +variable "api_token" { + description = "Nubes API token" + type = string + sensitive = true +} + +variable "api_endpoint" { + description = "Deck API endpoint" + type = string + default = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" +} + +variable "s3_uid" { + description = "UID существующего nubes_s3 (root S3 service)" + type = string +} + +variable "bucket_name" { + description = "Имя создаваемого бакета" + type = string +} + +variable "s3cfg_path" { + description = "Путь до .s3cfg_registry для доступа к S3" + type = string + default = "/home/naeel/remote_dev/terraform/secrets/.s3cfg_registry" +} + +variable "demo_timeout_sec" { + description = "Таймаут ожидания события в функции" + type = number + default = 25 +} diff --git a/TEST_STAND/S3_EVENT_FUNCTION_POC/versions.tf b/TEST_STAND/S3_EVENT_FUNCTION_POC/versions.tf new file mode 100644 index 0000000..742ad35 --- /dev/null +++ b/TEST_STAND/S3_EVENT_FUNCTION_POC/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.5.0" + + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "2.1.23" + } + } +}