diff --git a/.dockerignore b/.dockerignore index bfbdee64..4cb8c68a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,3 +16,4 @@ commitlint.config.json dist build reports +devScripts \ No newline at end of file diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml index af576d04..e248d2d5 100644 --- a/.github/workflows/pull_request.yaml +++ b/.github/workflows/pull_request.yaml @@ -1,8 +1,196 @@ name: pull_request -on: [pull_request] +on: [pull_request, workflow_dispatch] jobs: - pull_request: - uses: MapColonies/shared-workflows/.github/workflows/pull_request.yaml@master - secrets: inherit + eslint: + name: Run eslint + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: 12 + + - name: Install dependencies + run: npm ci + + - name: Run linters + uses: wearerequired/lint-action@v1 + with: + github_token: ${{ secrets.github_token }} + # Enable linters + eslint: true + prettier: true + eslint_extensions: ts + + - name: OpenAPI Lint Checks + uses: nwestfall/openapi-action@v1.0.2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + file: ./openapi3.yaml + + integration-test: + name: Run Integration Tests + runs-on: ubuntu-latest + container: node:16 + + services: + # Label used to access the service container + elasticsearch: + # Docker Hub image + image: elasticsearch:8.13.0 + env: + discovery.type: single-node + xpack.security.enabled: false + xpack.security.enrollment.enabled: false + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + ports: + - 9200:9200 + # Set health checks to wait until elastic has started + options: >- + --health-cmd "curl -f http://localhost:9200/_cluster/health || exit 1" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + minio: + # Docker Hub image + image: minio/minio:edge-cicd + env: + MINIO_ROOT_USER: minio + MINIO_ROOT_PASSWORD: minio123 + ports: + - 9000:9000 + # Set health checks to wait until elastic has started + options: >- + --health-cmd "curl -f http://localhost:9000/minio/health/live || exit 1" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + strategy: + matrix: + node: [18.x, 20.x] + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + + - name: Install Node.js dependencies + run: npm ci + + - name: Wait for Elasticsearch + run: | + until curl -s http://elasticsearch:9200/_cluster/health | grep '"status":"green"'; do + echo "Waiting for Elasticsearch..."; + sleep 5; + done + + - name: Run integration tests + run: npm run test:integration + + - uses: actions/upload-artifact@v4 + with: + name: Test Reporters + path: reports/** + + unit-test: + name: Run Unit Tests + runs-on: ubuntu-latest + container: node:16 + + services: + # Label used to access the service container + elasticsearch: + # Docker Hub image + image: elasticsearch:8.13.0 + env: + discovery.type: single-node + xpack.security.enabled: false + xpack.security.enrollment.enabled: false + ES_JAVA_OPTS: "-Xms512m -Xmx512m" + ports: + - 9200:9200 + # Set health checks to wait until elastic has started + options: >- + --health-cmd "curl -f http://localhost:9200/_cluster/health || exit 1" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + minio: + # Docker Hub image + image: minio/minio:edge-cicd + env: + MINIO_ROOT_USER: minio + MINIO_ROOT_PASSWORD: minio123 + ports: + - 9000:9000 + # Set health checks to wait until elastic has started + options: >- + --health-cmd "curl -f http://localhost:9000/minio/health/live || exit 1" + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + strategy: + matrix: + node: [18.x, 20.x] + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node }} + + - name: Install Node.js dependencies + run: npm ci + + - name: Wait for Elasticsearch + run: | + until curl -s http://elasticsearch:9200/_cluster/health | grep '"status":"green"'; do + echo "Waiting for Elasticsearch..."; + sleep 5; + done + + - name: Run unit tests + run: npm run test:unit + + - uses: actions/upload-artifact@v4 + with: + name: Test Reporters + path: reports/** + + security: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: Run Snyk to check for vulnerabilities + uses: snyk/actions/node@master + continue-on-error: true + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + + build_image: + name: Build Image + runs-on: ubuntu-latest + + steps: + - name: Check out Git repository + uses: actions/checkout@v2 + + - name: build Docker image + run: docker build -t test-build:latest . diff --git a/config/custom-environment-variables.json b/config/custom-environment-variables.json index ed72d6e5..4a8fdc81 100644 --- a/config/custom-environment-variables.json +++ b/config/custom-environment-variables.json @@ -67,25 +67,39 @@ } } }, - "postgresql": { - "host": "POSTGRES_URL", + "s3": { + "endpoint": "S3_ENDPOINT", + "credentials": { + "accessKeyId": "S3_ACCESS_KEY", + "secretAccessKey": "S3_SECRET_KEY" + }, + "region": "S3_REGION", + "files": { + "__name": "S3_FILES_DATA", + "__format": "json" + } + }, + "redis": { + "host": "REDIS_HOST", "port": { - "__name": "POSTGRES_PORT", + "__name": "REDIS_PORT", "__format": "number" }, - "username": "POSTGRES_USERNAME", - "password": "POSTGRES_PASSWORD", + "username": "REDIS_USERNAME", + "password": "REDIS_PASSWORD", "enableSslAuth": { - "__name": "POSTGRES_ENABLE_SLL_AUTH", + "__name": "REDIS_ENABLE_SSL_AUTH", "__format": "boolean" }, "sslPaths": { - "ca": "POSTGRES_CA_PATH", - "key": "POSTGRES_KEY_PATH", - "cert": "POSTGRES_CERT_PATH" + "ca": "REDIS_CA_PATH", + "key": "REDIS_KEY_PATH", + "cert": "REDIS_CERT_PATH" }, - "database": "POSTGRES_DB_NAME", - "schema": "POSTGRES_DB_SCHEMA" + "database": { + "__name": "REDIS_DATABASE", + "__format": "number" + } } }, "application": { @@ -104,6 +118,11 @@ "__name": "NAME_TRANSLATION_KEYS", "__format": "json" }, - "mainLanguageRegex": "MAIN_LANGUAGE_REGEX" + "controlObjectDisplayNamePrefixes": { + "__name": "CONTROL_DISPLAY_KEY_TRANSLATIONS", + "__format": "json" + }, + "mainLanguageRegex": "MAIN_LANGUAGE_REGEX", + "site": "SITE" } } diff --git a/config/default.json b/config/default.json index cf9186b0..9c165b10 100644 --- a/config/default.json +++ b/config/default.json @@ -28,22 +28,21 @@ "db": { "elastic": { "control": { - "node": "http://localhost:9200", + "node": "http://control_elastic:9200", "auth": { - "username": "control", - "password": "password" + "username": "elastic", + "password": "changeme" }, "requestTimeout": 60000, "properties": { - "index": "control_index", - "defaultResponseLimit": 3 + "index": "control_index" } }, "geotext": { - "node": "http://localhost:9200", + "node": "http://geotext_elastic:9200", "auth": { - "username": "geotext", - "password": "password" + "username": "elastic", + "password": "changeme" }, "requestTimeout": 60000, "properties": { @@ -52,25 +51,37 @@ "placetypes": "placetypes_index", "hierarchies": "hierarchies_index" }, - "defaultResponseLimit": 3, "textTermLanguage": "en" } } }, - "postgresql": { - "type": "postgres", - "host": "localhost", - "port": 5432, - "username": "postgres", - "password": "postgres", + "s3": { + "endpoint": "http://s3:9000", + "credentials": { + "accessKeyId": "accessKeyId", + "secretAccessKey": "secretAccessKey" + }, + "forcePathStyle": true, + "region": "local", + "files": { + "latLonConvertionTable": { + "bucket": "geocoding", + "fileName": "table.json" + } + } + }, + "redis": { + "host": "REDIS_HOST", + "port": 6379, + "username": "", + "password": "", "enableSslAuth": false, "sslPaths": { "ca": "", "key": "", "cert": "" }, - "database": "postgres", - "schema": "geocoder" + "database": 0 } }, "application": { @@ -85,12 +96,10 @@ "hierarchy": 1.1, "viewbox": 1.1 }, - "sources": { - "SOURCE_A": "a", - "SOURCE_B": "b" - }, + "sources": {}, "regions": {}, - "nameTranslationsKeys": ["en", "fr"], + "controlObjectDisplayNamePrefixes": {}, + "nameTranslationsKeys": [], "mainLanguageRegex": "[a-zA-Z]" } } diff --git a/config/test.json b/config/test.json index 6c4f3c2f..8305cd32 100644 --- a/config/test.json +++ b/config/test.json @@ -2,19 +2,18 @@ "db": { "elastic": { "control": { - "node": "http://localhost:9200", + "node": "http://elasticsearch:9200", "auth": { "username": "elastic", "password": "changeme" }, "requestTimeout": 60000, "properties": { - "index": "control_index", - "defaultResponseLimit": 3 + "index": "control_gil_v5_test" } }, "geotext": { - "node": "http://localhost:9200", + "node": "http://elasticsearch:9200", "auth": { "username": "elastic", "password": "changeme" @@ -22,29 +21,41 @@ "requestTimeout": 60000, "properties": { "index": { - "geotext": "geotext_index", - "placetypes": "placetypes_index", - "hierarchies": "hierarchies_index" + "geotext": "geotext_index_test", + "placetypes": "placetypes_index_test", + "hierarchies": "hierarchies_index_test" }, - "defaultResponseLimit": 3, "textTermLanguage": "en" } } }, - "postgresql": { - "type": "postgres", + "s3": { + "endpoint": "http://minio:9000", + "credentials": { + "accessKeyId": "minio", + "secretAccessKey": "minio123" + }, + "region": "local", + "forcePathStyle": true, + "files": { + "latLonConvertionTable": { + "bucket": "geocoding-test", + "fileName": "table.json" + } + } + }, + "redis": { "host": "localhost", - "port": 5432, - "username": "postgres", - "password": "postgres", + "port": 6379, + "username": "", + "password": "", "enableSslAuth": false, "sslPaths": { "ca": "", "key": "", "cert": "" }, - "database": "postgres", - "schema": "geocoder" + "database": 1 } }, "application": { @@ -60,10 +71,19 @@ "viewbox": 1.1 }, "sources": { - "OSM": "OSM" + "OSM": "OSM", + "GOOGLE": "GOOGLE" }, "regions": { - "USA": ["New York", "Los Angeles"] + "USA": ["New York", "Los Angeles"], + "FRANCE": ["Paris"] + }, + "controlObjectDisplayNamePrefixes": { + "TILE": "Tile", + "SUB_TILE": "Sub Tile", + "ROUTE": "Route", + "ITEM": "Item", + "CONTROL_POINT": "Control Point" }, "nameTranslationsKeys": ["en", "fr"], "mainLanguageRegex": "[a-zA-Z]" diff --git a/dataSource.ts b/dataSource.ts deleted file mode 100644 index ad31f0f6..00000000 --- a/dataSource.ts +++ /dev/null @@ -1,13 +0,0 @@ -import config from 'config'; -import { DataSource } from 'typeorm'; -import { createConnectionOptions } from './src/common/postgresql'; -import { PostgresDbConfig } from './src/common/interfaces'; - -const connectionOptions = config.get('db.postgresql'); - -export const appDataSource = new DataSource({ - ...createConnectionOptions(connectionOptions), - entities: ['src/**/DAL/*.ts'], - migrationsTableName: 'migrations_table', - migrations: ['db/migrations/*.ts'], -}); diff --git a/devScripts/controlElasticsearchData.json b/devScripts/controlElasticsearchData.json index 856ca077..73da7efd 100644 --- a/devScripts/controlElasticsearchData.json +++ b/devScripts/controlElasticsearchData.json @@ -1,7 +1,6 @@ [ { "_id": "CONTROL.ITEMS", - "_score": 1, "_source": { "type": "Feature", "id": 27, @@ -53,70 +52,322 @@ } }, { - "_id": "CONTROL.SUB_TILES", - "_score": 1, + "_id": "CONTROL.ITEMS1", + "_source": { + "type": "Feature", + "id": 211, + "geometry": { + "coordinates": [ + [ + [12.432792582620323, 41.9327692706986], + [12.432648028923637, 41.93209008126263], + [12.43295235249525, 41.93189198298137], + [12.435105441764364, 41.93235609798671], + [12.435516278586334, 41.93274663116725], + [12.43637599267521, 41.93308056343986], + [12.436026020567567, 41.93386161853829], + [12.432792582620323, 41.9327692706986] + ] + ], + "type": "Polygon" + }, + "properties": { + "OBJECTID": 211, + "F_CODE": 23000, + "F_ATT": 6020, + "VIEW_SCALE_50K_CONTROL": 6040, + "NAME": null, + "GFID": "{656eb8b8-1056-4eb5-955e-b670996cbb78}", + "OBJECT_COMMAND_NAME": "1234", + "SUB_TILE_NAME": null, + "TILE_NAME": "RIT", + "TILE_ID": "36, 7300, 3560", + "SUB_TILE_ID": "37", + "SHAPE.AREA": 2.65, + "SHAPE.LEN": 0.0044192097656775, + "LAYER_NAME": "CONTROL.ITEMS", + "ENTITY_HEB": "hotel", + "TYPE": "ITEM" + } + } + }, + { + "_id": "CONTROL.ITEMS2", "_source": { "type": "Feature", - "id": 13668, + "id": 212, "geometry": { "coordinates": [ [ - [27.149158174343427, 35.63159611670335], - [27.149274355343437, 35.64061707270338], - [27.138786228343463, 35.640716597703374], - [27.13867103934342, 35.631695606703374], - [27.149158174343427, 35.63159611670335] + [12.454224879037952, 41.93533128513971], + [12.4536294370983, 41.93493944442386], + [12.453354617740985, 41.934479454425286], + [12.453308814515196, 41.93386612926582], + [12.453789748389482, 41.933065390320394], + [12.454660009686364, 41.93255427509931], + [12.455667680662373, 41.93265649847106], + [12.456217319376208, 41.933355020461335], + [12.456217319376208, 41.934309086916585], + [12.45569058227531, 41.935092773686364], + [12.454957730656645, 41.93534832163783], + [12.454224879037952, 41.93533128513971] ] ], "type": "Polygon" }, + "properties": { + "OBJECTID": 212, + "F_CODE": 23001, + "F_ATT": 6021, + "VIEW_SCALE_50K_CONTROL": 6041, + "NAME": null, + "GFID": "{69a2805c-3977-4123-b97d-c149287af9d4}", + "OBJECT_COMMAND_NAME": "1235", + "SUB_TILE_NAME": null, + "TILE_NAME": "RIT", + "TILE_ID": "36, 7310, 3561", + "SUB_TILE_ID": "37", + "SHAPE.AREA": 2.65, + "SHAPE.LEN": 0.0044192097656775, + "LAYER_NAME": "CONTROL.ITEMS", + "ENTITY_HEB": "oplympic stadium", + "TYPE": "ITEM" + } + } + }, + { + "_id": "CONTROL.ITEMS3", + "_source": { + "type": "Feature", + "id": 213, + "geometry": { + "coordinates": [ + [ + [12.508591887289015, 41.908719199148294], + [12.5109416268715, 41.90414674778023], + [12.515619548794433, 41.905558627360875], + [12.514541686600694, 41.906697734719614], + [12.514929038978266, 41.9065233923005], + [12.515546733877699, 41.90711121610428], + [12.51436197480865, 41.90815873484965], + [12.513612640013008, 41.90876915151631], + [12.512974692823434, 41.90872393566693], + [12.512863305218872, 41.90807583830912], + [12.513561316395936, 41.90754911551221], + [12.513962675013943, 41.907221129768516], + [12.51419876831929, 41.906986853205694], + [12.513954805237745, 41.906893142339726], + [12.512516460769376, 41.90804005137895], + [12.511855399516293, 41.90857302052004], + [12.51176096219396, 41.908660872149284], + [12.508762577222939, 41.908924426310676], + [12.508591887289015, 41.908719199148294] + ] + ], + "type": "Polygon" + }, + "properties": { + "OBJECTID": 213, + "F_CODE": 23002, + "F_ATT": 6022, + "VIEW_SCALE_50K_CONTROL": 6042, + "NAME": null, + "GFID": "{935265e6-402f-4824-a193-e8725f73212e}", + "OBJECT_COMMAND_NAME": "1236", + "SUB_TILE_NAME": null, + "TILE_NAME": "RIT", + "TILE_ID": "36, 7310, 3581", + "SUB_TILE_ID": "38", + "SHAPE.AREA": 2.65, + "SHAPE.LEN": 0.0044192097656775, + "LAYER_NAME": "CONTROL.ITEMS", + "ENTITY_HEB": "hospital", + "TYPE": "ITEM" + } + } + }, + { + "_id": "CONTROL.SUB_TILES", + "_source": { + "type": "Feature", "properties": { "OBJECTID": 13668, "SUB_TILE_ID": "65", - "TILE_NAME": "GRC", + "TILE_NAME": "RIT", "NAME": "somePlace", - "ZON": 35, + "ZON": 37, "LAYER_NAME": "CONTROL.SUB_TILES", "TYPE": "SUB_TILE" + }, + "geometry": { + "coordinates": [ + [ + [12.439530324602458, 41.93031190061167], + [12.439646505602468, 41.9393328566117], + [12.429158378602494, 41.939432381611695], + [12.429043189602453, 41.930411390611695], + [12.439530324602458, 41.93031190061167] + ] + ], + "type": "Polygon" + } + } + }, + { + "_id": "CONTROL.SUB_TILES1", + "_source": { + "type": "Feature", + "properties": { + "OBJECTID": 13669, + "SUB_TILE_ID": "66", + "TILE_NAME": "RIT", + "NAME": "somePlace", + "ZON": 37, + "LAYER_NAME": "CONTROL.SUB_TILES", + "TYPE": "SUB_TILE" + }, + "geometry": { + "coordinates": [ + [ + [12.44999804325252, 41.930226156898485], + [12.45011422425253, 41.939247112898514], + [12.439626097252557, 41.93934663789851], + [12.439510908252515, 41.93032564689851], + [12.44999804325252, 41.930226156898485] + ] + ], + "type": "Polygon" } } }, { "_id": "CONTROL.ROUTES", - "_score": 1, "_source": { "type": "Feature", "id": 2, "geometry": { "coordinates": [ - [13.448493352142947, 52.31016611400918], - [13.447219581381603, 52.313370282889224], - [13.448088381125075, 52.31631514453963], - [13.450458681234068, 52.31867376333767], - [13.451112278530388, 52.32227665244022], - [13.449728938644029, 52.32463678850752], - [13.445021899434977, 52.32863442881066], - [13.444723882330948, 52.340023400115086], - [13.446229682887974, 52.34532799609971] + [12.443243654365062, 41.93890891937724], + [12.442636325462843, 41.93804302794496], + [12.442828646282095, 41.93725242115204], + [12.443405608739027, 41.93556576056804], + [12.44388471483822, 41.93370245333628], + [12.445132826188399, 41.93084467089824], + [12.445812354584433, 41.93031849813403], + [12.445819951137906, 41.930296776658196] ], "type": "LineString" }, "properties": { "OBJECTID": 2, - "OBJECT_COMMAND_NAME": "route96", + "OBJECT_COMMAND_NAME": "via camillucciaA", "FIRST_GFID": null, "SHAPE_Length": 4.1, "F_CODE": 8754, "F_ATT": 951, "LAYER_NAME": "CONTROL.ROUTES", - "ENTITY_HEB": "route96", + "ENTITY_HEB": "route", + "TYPE": "ROUTE" + } + } + }, + { + "_id": "CONTROL.ROUTES2", + "_source": { + "type": "Feature", + "id": 3, + "geometry": { + "coordinates": [ + [12.445818466287847, 41.93029376141277], + [12.446047161911423, 41.930040913942264], + [12.446171038707206, 41.9297762500957], + [12.446167862379838, 41.92945014491775], + [12.446075748864388, 41.9286136066203] + ], + "type": "LineString" + }, + "properties": { + "OBJECTID": 3, + "OBJECT_COMMAND_NAME": "via camillucciaB", + "FIRST_GFID": null, + "SHAPE_Length": 4.1, + "F_CODE": 8754, + "F_ATT": 951, + "LAYER_NAME": "CONTROL.ROUTES", + "ENTITY_HEB": "route", + "TYPE": "ROUTE" + } + } + }, + { + "_id": "CONTROL.ROUTES1", + "_source": { + "type": "Feature", + "id": 3, + "geometry": { + "coordinates": [ + [12.472759211857749, 41.932073169074016], + [12.475880836863752, 41.93239148035457] + ], + "type": "LineString" + }, + "properties": { + "OBJECTID": 3, + "OBJECT_COMMAND_NAME": "olimpiade", + "FIRST_GFID": null, + "SHAPE_Length": 4.1, + "F_CODE": 87541, + "F_ATT": 9511, + "LAYER_NAME": "CONTROL.ROUTES", + "ENTITY_HEB": "route", "TYPE": "ROUTE" } } }, + { + "_id": "CONTROL.ROUTES2", + "_source": { + "type": "Feature", + "id": 4, + "geometry": { + "coordinates": [12.475638293442415, 41.932360642739155], + "type": "Point" + }, + "properties": { + "OBJECTID": 4, + "OBJECT_COMMAND_NAME": "111", + "TIED_TO": "olimpiade", + "F_CODE": 8754111, + "F_ATT": 951111, + "ENTITY_HEB": "control point", + "LAYER_NAME": "CONTROL_GIL_GDB.CTR_CONTROL_POINT_CROSS_N", + "TYPE": "ITEM" + } + } + }, + { + "_id": "CONTROL.ROUTES3", + "_source": { + "type": "Feature", + "id": 5, + "geometry": { + "coordinates": [12.474175672012962, 41.932217551210556], + "type": "Point" + }, + "properties": { + "OBJECTID": 4, + "OBJECT_COMMAND_NAME": "112", + "TIED_TO": "olimpiade", + "F_CODE": 875411, + "F_ATT": 95111, + "ENTITY_HEB": "control point", + "LAYER_NAME": "CONTROL_GIL_GDB.CTR_CONTROL_POINT_CROSS_N", + "TYPE": "ITEM" + } + } + }, { "_id": "CONTROL.TILES", - "_score": 1, "_source": { "type": "Feature", "id": 52, @@ -141,5 +392,31 @@ "TYPE": "TILE" } } + }, + { + "_id": "CONTROL.TILES1", + "_source": { + "type": "Feature", + "properties": { + "OBJECTID": 53, + "ZONE": "37", + "TILE_NAME": "RIC", + "TILE_ID": null, + "LAYER_NAME": "CONTROL.TILES", + "TYPE": "TILE" + }, + "geometry": { + "coordinates": [ + [ + [12.64750356570994, 41.854073129598774], + [12.64478277570987, 41.94417235759876], + [12.536787035709892, 41.941850298598766], + [12.539620755710018, 41.85175890959876], + [12.64750356570994, 41.854073129598774] + ] + ], + "type": "Polygon" + } + } } ] diff --git a/devScripts/geotextElasticsearchData.json b/devScripts/geotextElasticsearchData.json index 3d0bddda..0581ea2c 100644 --- a/devScripts/geotextElasticsearchData.json +++ b/devScripts/geotextElasticsearchData.json @@ -1,7 +1,7 @@ [ { "_id": "12cd98ee-82eb-415e-a1d8-e1667a6f2e7f", - "_index": "geotext_index", + "_index": "geotext", "_source": { "source": "OSM", "layer_name": "osm_airports", @@ -66,7 +66,7 @@ }, { "_id": "8b5009b4-b643-4856-905d-ac95a2040aca", - "_index": "geotext_index", + "_index": "geotext", "_source": { "source": "OSM", "layer_name": "osm_airports", @@ -117,7 +117,7 @@ }, { "_id": "2bd0d4bb-af93-4211-9499-174c56feddb5", - "_index": "geotext_index", + "_index": "geotext", "_source": { "source": "OSM", "layer_name": "osm_airports", @@ -170,7 +170,7 @@ }, { "_id": "ba951f2f-08c2-446e-845d-e2b0547d5d8c", - "_index": "geotext_index", + "_index": "geotext", "_source": { "source": "OSM", "layer_name": "osm_ports", @@ -222,9 +222,63 @@ "text_language": "en" } }, + { + "_id": "1bb11f54-939e-457b-bf68-a3920ccf629c", + "_index": "geotext", + "_source": { + "source": "GOOGLE", + "layer_name": "google_ports", + "ingestion_timestamp": "2024-07-17 00:00:00", + "timestamp": "11/12/2023 00:00:00", + "source_id": ["{1bb11f54-939e-457b-bf68-a3920ccf629c}"], + "sensitivity": "non-sensitive", + "placetype": "transportation", + "sub_placetype": "port", + "wkt": "POLYGON ((-118.2505781304088 33.7502674389752, -118.25604403409116 33.76075151051916, -118.27057180697577 33.748593059782564, -118.27503083555426 33.741097783576635, -118.2747911028351 33.734798055529765, -118.27215404292296 33.73136889520775, -118.26807858669537 33.720881194108856, -118.26424286318695 33.721997816398385, -118.26640045650717 33.72901625632974, -118.2431463824787 33.735794882347946, -118.24492040460113 33.739303607948656, -118.25072193640723 33.73794798097781, -118.25220827926702 33.74193505797223, -118.24937943317966 33.74508471776615, -118.24798898340768 33.74783559181691, -118.24909175391655 33.74803492708783, -118.25096166912684 33.74600168558719, -118.25326310323155 33.745363795966625, -118.25278363779321 33.74687877606813, -118.2505781304088 33.7502674389752))", + "geo_json": { + "type": "Polygon", + "coordinates": [ + [ + [-118.2505781304088, 33.7502674389752], + [-118.25604403409116, 33.76075151051916], + [-118.27057180697577, 33.748593059782564], + [-118.27503083555426, 33.741097783576635], + [-118.2747911028351, 33.734798055529765], + [-118.27215404292296, 33.73136889520775], + [-118.26807858669537, 33.720881194108856], + [-118.26424286318695, 33.721997816398385], + [-118.26640045650717, 33.72901625632974], + [-118.2431463824787, 33.735794882347946], + [-118.24492040460113, 33.739303607948656], + [-118.25072193640723, 33.73794798097781], + [-118.25220827926702, 33.74193505797223], + [-118.24937943317966, 33.74508471776615], + [-118.24798898340768, 33.74783559181691], + [-118.24909175391655, 33.74803492708783], + [-118.25096166912684, 33.74600168558719], + [-118.25326310323155, 33.745363795966625], + [-118.25278363779321, 33.74687877606813], + [-118.2505781304088, 33.7502674389752] + ] + ] + }, + "centroid": { + "type": "Point", + "coordinates": [-118.25908860901649, 33.740816352314006] + }, + "geometry_type": "Polygon", + "geometry_hash": "ffbc4f66c802719b5bcffaca238ee1d3", + "region": ["USA"], + "sub_region": ["Los Angeles"], + "name": "Port of Los Angeles", + "text": ["Port of Los Angeles"], + "translated_text": ["Puerto de Los Ángeles"], + "text_language": "en" + } + }, { "_id": "2b3dd420-d265-4665-94a6-3e13650b7a2d", - "_index": "geotext_index", + "_index": "geotext", "_source": { "source": "OSM", "layer_name": "osm_schools", @@ -233,7 +287,7 @@ "source_id": ["{1a5b981b-bb0e-44dd-b9e2-424b92f2de49}"], "sensitivity": "non-sensitive", "placetype": "education", - "sub_placetype": "elementary school", + "sub_placetype": "school", "wkt": "POLYGON ((-118.30812263653988 33.71684417247593, -118.30861990876181 33.71674433152869, -118.30879709771484 33.71635922964194, -118.30619642115158 33.71550819588987, -118.30586490633668 33.715921827872904, -118.30587062210924 33.716183318328746, -118.30812263653988 33.71684417247593))", "geo_json": { "coordinates": [ @@ -263,9 +317,48 @@ "text_language": "en" } }, + { + "_id": "dc02a3f9-156a-4f61-85bd-fd040cd322a3", + "_index": "geotext", + "_source": { + "source": "OSM", + "layer_name": "osm_schools", + "ingestion_timestamp": "2024-07-17 00:00:00", + "timestamp": "11/12/2023 00:00:00", + "source_id": ["{dc02a3f9-156a-4f61-85bd-fd040cd322a3}"], + "sensitivity": "non-sensitive", + "placetype": "education", + "sub_placetype": "school", + "wkt": "POLYGON ((2.346441270696971 48.88088750665477, 2.3462780852304945 48.88018258877358, 2.347503576087604 48.87999951892243, 2.347737155284733 48.88070864783427, 2.346441270696971 48.88088750665477))", + "geo_json": { + "coordinates": [ + [ + [2.346441270696971, 48.88088750665477], + [2.3462780852304945, 48.88018258877358], + [2.347503576087604, 48.87999951892243], + [2.347737155284733, 48.88070864783427], + [2.346441270696971, 48.88088750665477] + ] + ], + "type": "Polygon" + }, + "centroid": { + "type": "Point", + "coordinates": [2.346966023961727, 48.88044772323158] + }, + "geometry_type": "Polygon", + "geometry_hash": "791f6b24002bbc1d50cdea5015244da9 ", + "region": ["FRANCE"], + "sub_region": ["Paris"], + "name": "Wi School Paris 9", + "text": ["Wi School Paris 9"], + "translated_text": ["Ecole Wi Paris 9"], + "text_language": "en" + } + }, { "_id": "5a54ff54-53f6-4ad1-9d2a-cd20f333ee2b", - "_index": "hierarchies_index", + "_index": "hierarchies", "_source": { "geo_json": { "coordinates": [ @@ -291,7 +384,7 @@ }, { "_id": "a3f38afc-1ce6-443c-a302-a0103c2bcb7d", - "_index": "hierarchies_index", + "_index": "hierarchies", "_source": { "geo_json": { "coordinates": [ @@ -314,9 +407,34 @@ "text": "Los Angeles" } }, + { + "_id": "a3f38afc-1ce6-443c-a302-a0103c2bcb7d", + "_index": "hierarchies", + "_source": { + "geo_json": { + "coordinates": [ + [ + [2.226678539753607, 49.06838747927134], + [1.9344166918067742, 48.906487548202136], + [2.014124468519668, 48.56855190252173], + [2.6536844864307625, 48.53463335095324], + [2.902296837606201, 48.82159183126478], + [2.6460932696008683, 49.0124047223114], + [2.384196288972504, 49.05097737411208], + [2.226678539753607, 49.06838747927134] + ] + ], + "type": "Polygon" + }, + "hierarchy": "city", + "placetype": "city", + "region": "FRANCE", + "text": "Paris" + } + }, { "_id": "430ddc04-2370-415f-8761-b8c66a142f0a", - "_index": "placetypes_index", + "_index": "placetypes", "_source": { "placetype": "transportation", "sub_placetype": "airport", @@ -325,7 +443,7 @@ }, { "_id": "6941ab8e-7503-4f8a-94f2-4750b2698db9", - "_index": "placetypes_index", + "_index": "placetypes", "_source": { "placetype": "transportation", "sub_placetype": "port", @@ -334,11 +452,11 @@ }, { "_id": "6941ab8e-7503-4f8a-94f2-4750b2698db9", - "_index": "placetypes_index", + "_index": "placetypes", "_source": { - "placetype": "transportation", - "sub_placetype": "education", - "sub_placetype_keyword": "elementary school" + "placetype": "education", + "sub_placetype": "school", + "sub_placetype_keyword": "school" } } ] diff --git a/devScripts/importDataToElastic.ts b/devScripts/importDataToElastic.ts index f23aefb0..84a5d85b 100644 --- a/devScripts/importDataToElastic.ts +++ b/devScripts/importDataToElastic.ts @@ -1,27 +1,86 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ -import { Client } from '@elastic/elasticsearch'; -import config from '../config/default.json'; +import crypto from 'node:crypto'; +import httpStatusCodes from 'http-status-codes'; +import { Client, estypes } from '@elastic/elasticsearch'; +import { ElasticDbClientsConfig } from '../src/common/elastic/interfaces'; +import { elasticConfigPath } from '../src/common/constants'; +import { IConfig } from '../src/common/interfaces'; import controlData from './controlElasticsearchData.json'; import geotextData from './geotextElasticsearchData.json'; -const main = async (): Promise => { - const controlClient = new Client({ ...config.db.elastic.control }); - const geotextClient = new Client({ ...config.db.elastic.geotext }); +const main = async (config: IConfig): Promise => { + const elasticConfig = config.get(elasticConfigPath); + const controlClient = new Client({ ...elasticConfig.control }); + const geotextClient = new Client({ ...elasticConfig.geotext }); + + for (const { client, indices } of [ + { + client: controlClient, + indices: [elasticConfig.control.properties.index] as string[], + }, + { + client: geotextClient, + indices: Object.values(elasticConfig.geotext.properties.index as { [key: string]: string }), + }, + ]) { + for (const index of indices) { + try { + await client.indices.delete({ index }); + } catch (error) { + if ((error as estypes.ErrorCause).meta.statusCode !== httpStatusCodes.NOT_FOUND) { + throw error; + } + console.error(error); + } + } + } + + for (const { client, index, key } of [ + { + client: controlClient, + index: elasticConfig.control.properties.index as string, + key: 'geometry', + }, + { + client: geotextClient, + index: (elasticConfig.geotext.properties.index as { [key: string]: string }).geotext, + key: 'geo_json', + }, + { + client: geotextClient, + index: (elasticConfig.geotext.properties.index as { [key: string]: string }).hierarchies, + key: 'geo_json', + }, + ]) { + await client.indices.create({ + index: index, + body: { + mappings: { + properties: { + [key]: { + type: 'geo_shape', + }, + }, + }, + }, + }); + } for (const item of controlData) { await controlClient.index({ - index: config.db.elastic.control.properties.index, - id: item._id, + index: elasticConfig.control.properties.index as string, + id: crypto.randomUUID(), body: item._source, }); } for (const item of geotextData) { await geotextClient.index({ - index: item._index, - id: item._id, + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + index: (elasticConfig.geotext.properties.index as { [key: string]: string })[item._index] as string, + id: crypto.randomUUID(), body: { ...item._source, }, diff --git a/devScripts/importDataToS3.ts b/devScripts/importDataToS3.ts new file mode 100644 index 00000000..74aacee0 --- /dev/null +++ b/devScripts/importDataToS3.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +import { CreateBucketCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { S3Config, s3ConfigPath } from '../src/common/s3'; +import { IConfig } from '../src/common/interfaces'; +import mockDataJson from './latLonConvertions.json'; + +const main = async (config: IConfig): Promise => { + const s3Config = config.get(s3ConfigPath); + const s3Client = new S3Client({ ...s3Config }); + + if (!s3Config.files.latLonConvertionTable) { + throw new Error('No latLonConvertionTable file path provided'); + } + + const { bucket: Bucket, fileName: Key } = s3Config.files.latLonConvertionTable; + + try { + await s3Client.send(new CreateBucketCommand({ Bucket, ACL: 'public-read' })); + } catch (error) { + console.error(error); + } + + try { + await s3Client.send( + new PutObjectCommand({ + Bucket, + Key, + Body: Buffer.from(JSON.stringify(mockDataJson), 'utf-8'), + }) + ); + } catch (error) { + console.error(error); + throw error; + } +}; + +export default main; diff --git a/devScripts/importToPostgres.ts b/devScripts/importToPostgres.ts deleted file mode 100644 index 9e81b7a6..00000000 --- a/devScripts/importToPostgres.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { DataSource } from 'typeorm'; -import { createConnectionOptions } from '../src/common/postgresql'; -import config from '../config/test.json'; -import { PostgresDbConfig } from '../src/common/interfaces'; - -export default async function createDatabaseClient(): Promise { - const connectionOptions = config.db.postgresql as PostgresDbConfig; - const connection = new DataSource({ - ...createConnectionOptions(connectionOptions), - }); - - await connection.initialize(); - - await connection.query(`CREATE SCHEMA IF NOT EXISTS ${config.db.postgresql.schema};`); - - await connection.query(` - CREATE TABLE IF NOT EXISTS ${config.db.postgresql.schema}.tile_lat_lon - ( - pk integer NOT NULL, - tile_name text COLLATE pg_catalog."default", - zone text COLLATE pg_catalog."default", - min_x text COLLATE pg_catalog."default", - min_y text COLLATE pg_catalog."default", - ext_min_x integer, - ext_min_y integer, - ext_max_x integer, - ext_max_y integer, - CONSTRAINT tile_lat_lon_pkey PRIMARY KEY (pk) - ) - `); - - await connection.query(` - INSERT INTO ${config.db.postgresql.schema}.tile_lat_lon( - pk, tile_name, zone, min_x, min_y, ext_min_x, ext_min_y, ext_max_x, ext_max_y) - VALUES (1, 'BRN', '33', '360000', '5820000', 360000, 5820000, 370000, 5830000); - `); - - await connection.query(` - INSERT INTO ${config.db.postgresql.schema}.tile_lat_lon( - pk, tile_name, zone, min_x, min_y, ext_min_x, ext_min_y, ext_max_x, ext_max_y) - VALUES (2, 'BMN', '32', '480000', '5880000', 480000, 5880000, 490000, 5890000); - `); - - console.log('PGSQL: Table created and data inserted'); - - await connection.destroy(); -} diff --git a/devScripts/index.ts b/devScripts/index.ts index 1ec5acdc..7a759df3 100644 --- a/devScripts/index.ts +++ b/devScripts/index.ts @@ -1,10 +1,11 @@ +import config from 'config'; import importDataToElastic from './importDataToElastic'; -import importToPostgres from './importToPostgres'; +import importDataToS3 from './importDataToS3'; -importDataToElastic() +importDataToElastic(config) .then(() => console.log('Success import data to elastic')) .catch(console.error); -importToPostgres() - .then(() => console.log('Success import data to postgres')) +importDataToS3(config) + .then(() => console.log('Success import data to s3')) .catch(console.error); diff --git a/devScripts/latLonConvertions.json b/devScripts/latLonConvertions.json new file mode 100644 index 00000000..c406ea34 --- /dev/null +++ b/devScripts/latLonConvertions.json @@ -0,0 +1,24 @@ +{ + "items": [ + { + "tile_name": "BRN", + "zone": "33", + "min_x": "360000", + "min_y": "5820000", + "ext_min_x": 360000, + "ext_min_y": 5820000, + "ext_max_x": 370000, + "ext_max_y": 5830000 + }, + { + "tile_name": "BMN", + "zone": "32", + "min_x": "480000", + "min_y": "5880000", + "ext_min_x": 480000, + "ext_min_y": 5880000, + "ext_max_x": 490000, + "ext_max_y": 5890000 + } + ] +} diff --git a/helm/templates/configmap.yaml b/helm/templates/configmap.yaml index 69202f7f..a43002d5 100644 --- a/helm/templates/configmap.yaml +++ b/helm/templates/configmap.yaml @@ -10,6 +10,7 @@ data: RESPONSE_COMPRESSION_ENABLED: {{ .Values.env.responseCompressionEnabled | quote }} LOG_LEVEL: {{ .Values.env.logLevel | quote }} LOG_PRETTY_PRINT_ENABLED: {{ .Values.env.logPrettyPrintEnabled | quote }} + SITE: {{ .Values.env.site | quote }} {{ if .Values.env.tracing.enabled }} TELEMETRY_TRACING_ENABLED: 'true' TELEMETRY_TRACING_URL: {{ $tracingUrl }} @@ -18,5 +19,18 @@ data: TELEMETRY_METRICS_ENABLED: 'true' TELEMETRY_METRICS_URL: {{ $metricsUrl }} {{ end }} + {{- with .Values.redisConfig }} + REDIS_HOST: {{ .host }} + REDIS_DATABASE: {{ .database | quote}} + REDIS_PORT: {{ .port | quote }} + {{- if .sslAuth.enabled }} + REDIS_ENABLE_SSL_AUTH: "true" + REDIS_CERT_PATH: /tmp/certs/{{ .sslAuth.certFileName }} + REDIS_KEY_PATH: /tmp/certs/{{ .sslAuth.keyFileName }} + REDIS_CA_PATH: /tmp/certs/{{ .sslAuth.caFileName }} + {{- else }} + REDIS_ENABLE_SSL_AUTH: "false" + {{- end }} + {{- end }} npm_config_cache: /tmp/ {{- end }} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index 007efa2a..5484cfd7 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -53,6 +53,11 @@ spec: name: root-ca subPath: {{ quote .Values.caKey }} {{- end }} + {{- if .Values.redisConfig.sslAuth.enabled }} + - name: cert-conf + mountPath: /tmp/certs + readOnly: true + {{- end }} {{- if .Values.extraVolumeMounts -}} {{ toYaml .Values.extraVolumeMounts | nindent 12 }} {{- end }} @@ -109,6 +114,11 @@ spec: secret: secretName: {{ .Values.caSecretName }} {{- end }} + {{- if .Values.redisConfig.sslAuth.enabled }} + - name: cert-conf + secret: + secretName: {{ .Values.redisConfig.sslAuth.secretName }} + {{- end }} {{- if .Values.extraVolumes -}} {{ tpl (toYaml .Values.extraVolumes) . | nindent 8 }} {{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index 25fd7688..5fd8cc0e 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -62,6 +62,7 @@ env: protocol: TCP logLevel: info logPrettyPrintEnabled: false + site: main responseCompressionEnabled: true requestPayloadLimit: 1mb tracing: @@ -71,6 +72,19 @@ env: enabled: false url: http://localhost:55681/v1/metrics +redisConfig: + host: localhost + username: "" + password: "" + database: 0 + port: 6379 + sslAuth: + enabled: false + secretName: secret-name + certFileName: postgresql.crt + keyFileName: postgresql.key + caFileName: root.crt + resources: enabled: true value: diff --git a/openapi3.yaml b/openapi3.yaml index 8a19c7db..1d92fd88 100644 --- a/openapi3.yaml +++ b/openapi3.yaml @@ -1,22 +1,23 @@ -openapi: "3.0.1" +openapi: '3.0.1' info: - version: "1.0.0" - title: "New Geocoding" - description: "Geocoding api" + version: '0.1.0' + title: 'Geocoding' + description: |- + MapColonies Vector Geocoding api provides custom geodata search engine uniting multiple sources of data, query tiles, routes, items; Convertion functions - tile to/from WGS84 lat lng, WGS84 to/from US Army MGRS, WGS84 to/from UTM. license: name: MIT url: https://opensource.org/licenses/MIT tags: - - name: Query Based Search - - name: Exact Searches + - name: Location Name Based Search + - name: Control + - name: Convertions paths: - /v1/query: + /search/query: get: - operationId: searchByQuery - tags: - - Query Based Search - summary: "Search anything by query" - description: "This is for general queries. If known regex is found, the server will return results as if you searched for exact search. Else, the server will search using geotext engine to find a match." + operationId: GetSmartQuery + summary: 'Search anything' + description: |- + This is for general queries. the services will make a sophisticated guess. parameters: - name: query in: query @@ -24,619 +25,890 @@ paths: schema: type: string minLength: 3 - maxLength: 1000 - description: Text to search - title: Query + maxLength: 100 + title: 'Query' description: Text to search allowReserved: true - - name: limit + - $ref: '#/components/parameters/geo_context' + - $ref: '#/components/parameters/geo_context_mode' + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/disable_fuzziness' + responses: + 200: + description: 'OK
Will return valid GeoJSON FeatureCollection' + headers: + x-request-id: + schema: + type: string + minLength: 1 + maxLength: 36 + content: + application/json: + schema: + $ref: '#/components/schemas/genericGeocodingResponse' + 400: + '$ref': '#/components/responses/BadRequest' + 401: + '$ref': '#/components/responses/Unauthorized' + 403: + '$ref': '#/components/responses/Forbidden' + 500: + '$ref': '#/components/responses/InternalError' + security: + - x-api-key: [] + x-user-id: [] + + /search/location/query: + get: + operationId: locationGetQuery + summary: 'Search engine' + description: 'Search for geo-data. It serves data from multiple diverse mapcolonies vector sources and partners.' + tags: + - Location Name Based Search + parameters: + - name: query in: query - required: false + required: true schema: - type: integer - maximum: 1000 - minimum: 0 - description: Maximum results the service should return - default: 10 - title: Limit - description: Maximum results the service should return + type: string + minLength: 3 + maxLength: 100 + title: 'Query' + description: Text to search + allowReserved: true - name: source in: query required: false schema: type: array items: - $ref: "#/components/schemas/Source" - description: Sources to include (if not specified, all sources will be queried) + $ref: '#/components/schemas/Source' title: Source - description: Sources to include (if not specified, all sources will be queried) - - name: viewbox - in: query - required: false - allowReserved: true - schema: - type: string - description: |- - User's viewbox, used for biasing the results, or preferring results closer to the user - - Results close to this geometry will be preferred - - When providing a non-zero-area entity (e.g. polygon or bbox), results inside the entity will be most preferred - - Possible formats include a valid geoJSON, a valid WKT, an "x,y" point, or a "xmin,ymin,xmax,ymax" bounding box - title: Viewbox - description: |- - User's viewbox, used for biasing the results, or preferring results closer to the user - - Results close to this geometry will be preferred - - When providing a non-zero-area entity (e.g. polygon or bbox), results inside the entity will be most preferred - - Possible formats include a valid geoJSON, a valid WKT, an "x,y" point, or a "xmin,ymin,xmax,ymax" bounding box - - name: boundary - in: query - required: false - allowReserved: true - schema: - type: string - description: |- - A filter on the results. - - Results not touching this geometry will not be returned - - Possible formats include a valid geoJSON, a valid WKT, an "x,y" point, or a "xmin,ymin,xmax,ymax" bounding box - title: Boundary - description: |- - A filter on the results. - - Results not touching this geometry will not be returned - - Possible formats include a valid geoJSON, a valid WKT, an "x,y" point, or a "xmin,ymin,xmax,ymax" bounding box + description: | + Sources to include (if not specified, all sources will be queried) - name: region in: query required: false schema: type: array items: - $ref: "#/components/schemas/Region" + $ref: '#/components/schemas/Region' description: Regions to include (if not specified, all Regions will be queried) title: Region description: Regions to include (if not specified, all Regions will be queried) + - $ref: '#/components/parameters/geo_context' + - $ref: '#/components/parameters/geo_context_mode' + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/disable_fuzziness' responses: 200: - description: "OK (can be one of the following: tilesSchema, routesSchema, itemsSchema, subTileSchema)

WIP: Find out returned schema from geotext" + description: 'OK' + x-request-id: + schema: + type: string + minLength: 1 + maxLength: 36 content: application/json: schema: - anyOf: - - $ref: "#/components/schemas/tilesSchema" - - $ref: "#/components/schemas/routesSchema" - - $ref: "#/components/schemas/itemsSchema" - - $ref: "#/components/schemas/subTileSchema" + allOf: + - "$ref": "#/components/schemas/genericGeocodingResponse" + - type: "object" + required: + - "type" + - "features" + properties: + type: + type: "string" + enum: + - "FeatureCollection" + features: + type: "array" + items: + type: "object" + properties: + geometry: + oneOf: + - $ref: "#/components/schemas/Point" + - $ref: "#/components/schemas/Polygon" + properties: + type: "object" + properties: + placetype: + type: "string" + sub_placetype: + type: "string" 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] - /v1/query/regions: + - x-api-key: [] + x-user-id: [] + + /search/location/regions: get: - operationId: getRegions + operationId: locationGetRegions tags: - - Query Based Search - summary: "Get regions" + - Location Name Based Search + summary: 'Get all available regions to filter on using location query' responses: 200: - description: "All regions" + description: 'All regions' content: application/json: schema: type: array items: - type: string + type: string # return geojson with name property 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] - /v1/search/tiles: + - x-api-key: [] + x-user-id: [] + + /search/location/sources: get: - operationId: getTilesByQueryParams + operationId: locationGetSources tags: - - Exact Searches - summary: "Search tiles and sub tiles" - description: "Tiles are consist of 3 characters.

You can query and get results from 2 characters.

If you define a tile (full name of it) you can query the sub tile associated with it." - parameters: - - name: "tile" - in: "query" - description: "Tile name" - schema: - type: "string" - required: true - - name: "sub_tile" - in: "query" - description: "Sub tile number" - schema: - type: "number" - - $ref: "#/components/parameters/reduce_fuzzy_match" - - $ref: "#/components/parameters/size" + - Location Name Based Search + summary: 'Get all available sources to filter on using location query' responses: 200: - description: "OK" + description: 'All sources' content: application/json: schema: - $ref: "#/components/schemas/tilesSchema" + type: array + items: + type: string 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] - /v1/search/items: + - x-api-key: [] + x-user-id: [] + + /search/control/tiles: get: - operationId: getItemsByQueryParams + operationId: controlGetTilesByQueryParams tags: - - Exact Searches - summary: "Search control items" - description: "" + - Control + summary: 'Search tiles and sub tiles' + description: |- + Tiles are consisted of 3 characters.
+ You can query and get results from 2 characters.

If you define a tile (full name of it) you can query the sub tile associated with it.
+ You may also search tile based on 1 meter precision MGRS tile. parameters: - - name: "command_name" - in: "query" - description: "Object command name of the item" + - name: 'tile' + in: 'query' + description: 'Tile name' schema: - type: "string" - required: true - - - name: "tile" - in: "query" - description: "The tile the item in it (full name of it)" + type: 'string' + minLength: 2 + maxLength: 3 + - name: 'sub_tile' + in: 'query' + description: 'Sub tile number' schema: - type: "string" - required: false - - - name: "sub_tile" - in: "query" - description: "The sub tile the item in it (required if tile is defined)" + type: 'string' + pattern: '^[1-9][0-9]*$' + - name: mgrs + description: '1 meters MGRS Tile' + example: '18SUJ2338907395' + in: query schema: - type: "number" - - $ref: "#/components/parameters/geo_context" - - $ref: "#/components/parameters/reduce_fuzzy_match" - - $ref: "#/components/parameters/size" + type: string + - $ref: '#/components/parameters/geo_context' + - $ref: '#/components/parameters/geo_context_mode' + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/disable_fuzziness' responses: 200: - description: "OK" + description: 'OK' content: application/json: schema: - $ref: "#/components/schemas/itemsSchema" + $ref: '#/components/schemas/tilesSchema' 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] - /v1/search/routes: + - x-api-key: [] + x-user-id: [] + /search/control/items: get: - operationId: getRoutesByQueryParams + operationId: controlGetItemsByQueryParams tags: - - Exact Searches - summary: "Search routes and report control points" - description: "You can query and get control routes.

If you define a route (full name of it) you can query the control points associated with it. " + - Control + summary: 'Search control items' + description: '' parameters: - - name: "command_name" - in: "query" - description: "Object command name of the item" + - name: 'command_name' + in: 'query' + description: 'Object command name of the item' schema: - type: "string" + type: 'string' required: true - - name: "control_point" - in: "query" - description: "The associated report control point of the route" + + - name: 'tile' + in: 'query' + description: 'The tile the item in it (full name of it)' schema: - type: "number" - - $ref: "#/components/parameters/geo_context" - - $ref: "#/components/parameters/reduce_fuzzy_match" - - $ref: "#/components/parameters/size" + type: 'string' + minLength: 3 + maxLength: 3 + required: false + + - name: 'sub_tile' + in: 'query' + description: 'The sub tile the item in it (required if tile is defined)' + schema: + type: 'string' + pattern: '^[1-9][0-9]*$' + example: '66' + - $ref: '#/components/parameters/geo_context' + - $ref: '#/components/parameters/geo_context_mode' + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/disable_fuzziness' responses: 200: - description: "OK (can be routesSchema or itemsSchema)" + description: 'OK' content: application/json: schema: - anyOf: - - $ref: "#/components/schemas/routesSchema" - - $ref: "#/components/schemas/itemsSchema" + $ref: '#/components/schemas/itemsSchema' 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] - /v1/lookup/latlonToTile: + - x-api-key: [] + x-user-id: [] + /search/control/routes: get: - operationId: convertLatlonToTile + operationId: controlGetRoutesByQueryParams tags: - - Exact Searches - summary: "Convert a WGS84 coordinate to a tile" + - Control + summary: 'Search routes and report control points' + description: 'You can query and get control routes.

If you define a route (full name of it) you can query the control points associated with it. ' parameters: - - name: "lat" - in: "query" - description: "Latitude of the coordinate" + - name: 'command_name' + in: 'query' + description: 'Object command name of the item' schema: - type: "number" - minimum: -90 - maximum: 90 + type: 'string' required: true - - name: "lon" - in: "query" - description: "Longitude of the coordinate" + - name: 'control_point' + in: 'query' + description: 'The associated report control point of the route' schema: - type: "number" - minimum: -180 - maximum: 180 - required: true + type: 'string' + pattern: '^[1-9][0-9]*$' + - $ref: '#/components/parameters/geo_context' + - $ref: '#/components/parameters/geo_context_mode' + - $ref: '#/components/parameters/limit' + - $ref: '#/components/parameters/disable_fuzziness' responses: 200: - description: "OK" + description: 'OK (can be routesSchema or itemsSchema)' content: application/json: schema: - $ref: "#/components/schemas/subTileSchema" + anyOf: + - $ref: '#/components/schemas/routesSchema' + - $ref: '#/components/schemas/itemsSchema' 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] - /v1/lookup/tileToLatLon: + - x-api-key: [] + x-user-id: [] + /search/MGRS/tiles: get: - operationId: convertTileToLatlon + operationId: getMGRSToGeom tags: - - Exact Searches - summary: "Convert a tile to a geojson tile" + - MGRS + summary: 'Convert a MGRS string to Geometry in GeoJSON' parameters: - - name: "tile" - in: "query" - description: "Tile name" + - name: 'tile' + in: 'query' + description: 'MGRS tile string' schema: - type: "string" - required: true - - name: "sub_tile_number" - in: "query" - description: "An array describing sub tile number" - schema: - type: "array" - items: - type: "number" - minimum: 0 - maximum: 99 - minItems: 3 - maxItems: 3 + type: 'string' required: true responses: 200: - description: "OK" + description: 'OK' content: application/json: schema: - $ref: "#/components/schemas/tilesSchema" + $ref: '#/components/schemas/mgrsTileSchema' 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] - /v1/lookup/latlonToMgrs: + - x-api-key: [] + x-user-id: [] + /lookup/coordinates: get: - operationId: convertLatLonToMgrs + operationId: convertionGetLatLonToMgrs tags: - - Exact Searches - summary: "Convert a WGS84 coordinate to US Army MGRS" + - Convertions + summary: 'Convert a WGS84 coordinate to US Army MGRS / Control Grid' parameters: - - name: "lat" - in: "query" - description: "Latitude of the coordinate" + - name: 'lat' + in: 'query' + description: 'Latitude of the coordinate' schema: - type: "number" + type: 'number' minimum: -90 maximum: 90 required: true - - name: "lon" - in: "query" - description: "Longitude of the coordinate" + - name: 'lon' + in: 'query' + description: 'Longitude of the coordinate' schema: - type: "number" + type: 'number' minimum: -180 maximum: 180 required: true - - name: "accuracy" - in: "query" - description: "Accuracy in digits (5 for 1 m, 4 for 10 m, 3 for 100 m, 2 for 1 km, 1 for 10 km or 0 for 100 km). Optional, default is 5." + - name: 'target_grid' + in: query + description: Choose target grid schema: - type: "number" - minimum: 0 - maximum: 5 + type: string + enum: ['control', 'MGRS'] + required: true responses: 200: - description: "OK" + description: 'OK' content: application/json: schema: type: object properties: - mgrs: - type: string - pattern: ^\d{1,2}[^ABIOYZabioyz][A-Za-z]{2}([0-9][0-9])+$ + type: + type: "string" + enum: + - "Feature" + geocoding: + type: "object" + properties: + query: + type: "object" + properties: + lat: + type: number + lon: + type: number + response: + type: "object" + properties: + max_score: + type: number + results_count: + type: number + match_latency_ms: + type: number + bbox: + $ref: "#/components/schemas/BoundingBox" + geometry: + oneOf: + - $ref: "#/components/schemas/Point" + - $ref: "#/components/schemas/Polygon" + properties: + type: object + properties: + name: + type: string + additionalProperty1: + type: object 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] - /v1/lookup/mgrsToLatLon: - get: - operationId: convertMgrsToLatLon + - x-api-key: [] + x-user-id: [] + + /searches/feedback: + post: + operationId: postSearchFeedback tags: - - Exact Searches - summary: "Convert a US Army MGRS to WGS84 coordinate" + - Feedback + summary: 'Retrieve feedback from services about quality of results' parameters: - - name: "mgrs" - in: "query" - description: "MGRS string" + - name: choosen_result_id + in: 'query' + description: 'The result ID of chosen search' schema: - type: "string" - pattern: ^\d{1,2}[^ABIOYZabioyz][A-Za-z]{2}([0-9][0-9])+$ + type: string + minimum: 1 + maximum: 100 + required: true + - name: request_id + in: query + description: The request ID retrieved from headers of search API response + schema: + type: string + minimum: 1 + maximum: 100 required: true responses: - 200: - description: "OK" - content: - application/json: - schema: - type: object - properties: - lat: - type: number - lon: - type: number + 204: + description: 'OK - No Content' 400: - "$ref": "#/components/responses/BadRequest" + '$ref': '#/components/responses/BadRequest' + 404: + $ref: '#/components/responses/NotFound' 401: - "$ref": "#/components/responses/Unauthorized" + '$ref': '#/components/responses/Unauthorized' 403: - "$ref": "#/components/responses/Forbidden" + '$ref': '#/components/responses/Forbidden' 500: - "$ref": "#/components/responses/InternalError" + '$ref': '#/components/responses/InternalError' security: - - X-API-Key: [] - X-User-ID: [] + - x-api-key: [] + x-user-id: [] + components: responses: BadRequest: - description: "Invalid Request" + description: 'Invalid Request' content: application/json: schema: - $ref: "#/components/schemas/errorSchema" + $ref: '#/components/schemas/errorSchema' Unauthorized: - description: "Please provide a valid token" + description: 'Please provide a valid token' + content: + application/json: + schema: + $ref: '#/components/schemas/errorSchema' + NotFound: + description: 'Resource Not Found' content: application/json: schema: - $ref: "#/components/schemas/errorSchema" + $ref: '#/components/schemas/errorSchema' Forbidden: - description: "Token is not valid" + description: 'Token is not valid' content: application/json: schema: - $ref: "#/components/schemas/errorSchema" + $ref: '#/components/schemas/errorSchema' InternalError: - description: "Invalid Request" + description: 'Invalid Request' content: application/json: schema: - $ref: "#/components/schemas/errorSchema" + $ref: '#/components/schemas/errorSchema' parameters: - reduce_fuzzy_match: - name: "reduce_fuzzy_match" - in: "query" - description: "If an accurate result is obtained, only it will be returned (default is false)" + disable_fuzziness: + name: 'disable_fuzziness' + in: 'query' + description: |- + If an accurate result is obtained, only it will be returned schema: - type: "boolean" - - size: - name: "size" - in: "query" - description: "Maximum number of results (default is 3, range: 1-100)" + $ref: '#/components/schemas/disable_fuzziness' + limit: + name: 'limit' + in: 'query' + description: 'Maximum number of results' schema: - type: "number" - + default: 5 + minimum: 1 + maximum: 15 + type: 'number' geo_context: - name: "geo_context" - in: "query" - description: 'Geo context of search.

Supported queries:

*By bounding box, object with these keys: {"bbox":[minX,minY,maxX,maxY]}

*By point and radius, object with these keys: {"lon":value,"lat":value,"radius":value}' + in: 'query' + name: 'geo_context' + description: |- + Geo context of search. + + Supported queries: + * bounding box:
+ {"bbox":[minX,minY,maxX,maxY]} + * By point and radius (WGS84 and UTM are supported)
+ {"lon":value,"lat":value,"radius":value}
+ {"x":value,"y":value,"zone": number, "radius":value} + schema: + $ref: '#/components/schemas/geo_context' + geo_context_mode: + name: 'geo_context_mode' + in: 'query' + description: 'Choose whether geo_context query parameter will be a filter or a bias value' schema: - type: "string" + type: string + enum: ['filter', 'bias'] schemas: errorSchema: - type: "object" + type: 'object' required: - - "message" + - 'message' properties: message: - type: "string" + type: 'string' status: - type: "number" + type: 'number' subTileSchema: - type: "object" - description: "An object represnting a sub tile" + type: 'object' + description: 'An object represnting a sub tile' required: - tileName - subTileNumber properties: tileName: - type: "string" + type: 'string' subTileNumber: - type: "array" + type: 'array' items: - type: "number" + type: 'number' minimum: 0 maximum: 100 minItems: 3 maxItems: 3 - tilesSchema: - type: "object" - description: "GeoJson feature collection representing a tile (Polygon)" - required: - - "type" - - "features" + tileSchema: + type: 'object' properties: type: - type: "string" - enum: - - "FeatureCollection" - features: - type: "array" - items: - type: "object" - properties: - type: - type: "string" - enum: ["Feature"] - geometry: - $ref: "#/components/schemas/Geometry" - properties: - type: "object" - required: - - "TYPE" - - "TILE_NAME" - properties: - TYPE: - type: "string" - TILE_NAME: - type: "string" - SUB_TILE_ID: - type: "string" - SUB_TILE_NUMBER: - type: "array" - items: - type: "number" - minimum: 0 - maximum: 100 - minItems: 3 - maxItems: 3 - itemsSchema: - type: "object" - description: "GeoJson feature collection representing an item" - required: - - "type" - - "features" + type: 'string' + enum: ['Feature'] + geometry: + $ref: '#/components/schemas/Polygon' + properties: + type: 'object' + required: + - 'TYPE' + - 'tile_name' + properties: + TYPE: + type: 'string' + tile_name: + type: 'string' + sub_tile_id: + type: 'string' + SUB_TILE_NUMBER: + type: 'array' + items: + type: 'number' + minimum: 0 + maximum: 100 + minItems: 3 + maxItems: 3 + mgrsTileSchema: + type: 'object' properties: type: type: "string" enum: - - "FeatureCollection" - features: - type: "array" - items: - type: "object" - properties: - geometry: - $ref: "#/components/schemas/Geometry" + - "Feature" + geocoding: + type: "object" + properties: + query: + type: "object" + properties: + tile: + type: string + example: "18SUJ2339007393" + response: + type: "object" properties: + max_score: + type: number + results_count: + type: number + match_latency_ms: + type: number + + bbox: + $ref: "#/components/schemas/BoundingBox" + geometry: + oneOf: + - $ref: "#/components/schemas/Point" + - $ref: "#/components/schemas/Polygon" + properties: + type: object + properties: + score: + type: number + tilesSchema: + allOf: + - $ref: "#/components/schemas/genericGeocodingResponse" + - type: "object" + description: "GeoJson feature collection representing a tile (Polygon)" + required: + - "type" + - "features" + properties: + type: + type: "string" + enum: + - "FeatureCollection" + features: + type: "array" + items: + $ref: "#/components/schemas/tileSchema" + itemsSchema: + allOf: + - $ref: "#/components/schemas/genericGeocodingResponse" + - type: "object" + description: "GeoJson feature collection representing an item" + required: + - "type" + - "features" + properties: + type: + type: "string" + enum: + - "FeatureCollection" + features: + type: "array" + items: type: "object" - required: - - "TYPE" - - "OBJECT_COMMAND_NAME" - - "ENTITY_HEB" - - "SUB_TILE_ID" properties: - TYPE: - type: "string" - OBJECT_COMMAND_NAME: - type: "string" - ENTITY_HEB: - type: "string" - TILE_NAME: - type: "string" - SUB_TILE_ID: - type: "string" + geometry: + oneOf: + - $ref: "#/components/schemas/Point" + - $ref: "#/components/schemas/Polygon" + properties: + type: "object" + required: + - "TYPE" + - "object_command_name" + - "entity_heb" + - "sub_tile_id" + properties: + TYPE: + type: "string" + object_command_name: + type: "string" + entity_heb: + type: "string" + tile_name: + type: "string" + sub_tile_id: + type: "string" routesSchema: - type: "object" - description: "GeoJson feature collection representing a route (Line)" + allOf: + - $ref: "#/components/schemas/genericGeocodingResponse" + - type: "object" + description: "GeoJson feature collection representing a route (MultiLineString, LineString)" + required: + - "type" + - "features" + properties: + type: + type: "string" + enum: + - "FeatureCollection" + features: + type: "array" + items: + type: "object" + properties: + geometry: + oneOf: + - $ref: "#/components/schemas/LineString" + - $ref: "#/components/schemas/MultiLineString" + properties: + type: "object" + required: + - "TYPE" + - "object_command_name" + - "entity_heb" + properties: + TYPE: + type: "string" + object_command_name: + type: "string" + entity_heb: + type: "string" + SECTION: + type: "string" + genericGeocodingResponse: # we need discriminator for Control and Location + type: 'object' + description: 'GeoJson feature collection representing an item' required: - - "type" - - "features" + - 'type' + - 'features' + - 'geocoding' properties: type: - type: "string" + type: 'string' enum: - - "FeatureCollection" + - 'FeatureCollection' + geocoding: + type: object + required: + - 'query' + - 'response' + properties: + version: + type: string + pattern: '^\d{3}\.\d{3}\.\d{3}$' + example: 1.5.3 + query: + type: object + required: + - 'text' + - 'limit' + - 'geo_context' + - 'disable_fuzziness' + properties: + text: + type: string + minLength: 1 + maxLength: 100 + limit: + type: integer + minimum: 1 + maximum: 10 + geo_context: + $ref: '#/components/schemas/geo_context' + disable_fuzziness: + $ref: '#/components/schemas/disable_fuzziness' + response: + type: object + required: + - 'max_score' + - 'results_count' + - 'match_latency' + properties: + max_score: + type: number + minimum: 0 + maximum: 100 + results_count: + type: integer + minimum: 0 + maximum: 10 + match_latency_ms: + type: integer + minimum: 0 + maximum: 5000 features: - type: "array" + type: 'array' items: - type: "object" + type: 'object' properties: geometry: - $ref: "#/components/schemas/Geometry" + $ref: '#/components/schemas/Geometry' properties: - type: "object" + type: 'object' + additionalProperties: true required: - - "TYPE" - - "OBJECT_COMMAND_NAME" - - "ENTITY_HEB" + - id + - score + - matches + - regions + - display_name + - names properties: - TYPE: - type: "string" - OBJECT_COMMAND_NAME: - type: "string" - ENTITY_HEB: - type: "string" - SECTION: - type: "string" + score: + type: number + minimum: 0 + maximum: 100 + matches: + type: array + minItems: 1 + maxItems: 100 + items: + type: object + required: + - id + - source + - layer + properties: + layer: + type: string + source: + type: string + source_id: + type: array + items: + type: string + names: + type: object + required: + - display + - default + additionalProperties: + type: array + minItems: 1 + maxItems: 100 + items: + type: string + properties: + display: + type: string + default: + type: array + minItems: 1 + items: + type: string + regions: + type: array + minItems: 1 + maxItems: 100 + items: + type: object + required: + - name + - sub_regions_names + properties: + name: + type: string + sub_regions_names: + type: array + minItems: 0 + maxItems: 100 + items: + type: string + minLength: 1 + maxLength: 100 + Geometry: type: object description: Geojson geometry @@ -672,69 +944,69 @@ components: externalDocs: url: http://geojson.org/geojson-spec.html#id2 allOf: - - $ref: "#/components/schemas/Geometry" + - $ref: '#/components/schemas/Geometry' - properties: coordinates: - $ref: "#/components/schemas/Point3D" + $ref: '#/components/schemas/Point3D' LineString: type: object description: Geojson geometry externalDocs: url: http://geojson.org/geojson-spec.html#id3 allOf: - - $ref: "#/components/schemas/Geometry" + - $ref: '#/components/schemas/Geometry' - properties: coordinates: type: array items: - $ref: "#/components/schemas/Point3D" + $ref: '#/components/schemas/Point3D' Polygon: type: object description: Geojson geometry externalDocs: url: http://geojson.org/geojson-spec.html#id4 allOf: - - $ref: "#/components/schemas/Geometry" + - $ref: '#/components/schemas/Geometry' - properties: coordinates: type: array items: type: array items: - $ref: "#/components/schemas/Point3D" + $ref: '#/components/schemas/Point3D' MultiPoint: type: object description: Geojson geometry externalDocs: url: http://geojson.org/geojson-spec.html#id5 allOf: - - $ref: "#/components/schemas/Geometry" + - $ref: '#/components/schemas/Geometry' - properties: coordinates: type: array items: - $ref: "#/components/schemas/Point3D" + $ref: '#/components/schemas/Point3D' MultiLineString: type: object description: Geojson geometry externalDocs: url: http://geojson.org/geojson-spec.html#id6 allOf: - - $ref: "#/components/schemas/Geometry" + - $ref: '#/components/schemas/Geometry' - properties: coordinates: type: array items: type: array items: - $ref: "#/components/schemas/Point3D" + $ref: '#/components/schemas/Point3D' MultiPolygon: type: object description: Geojson geometry externalDocs: url: http://geojson.org/geojson-spec.html#id6 allOf: - - $ref: "#/components/schemas/Geometry" + - $ref: '#/components/schemas/Geometry' - properties: coordinates: type: array @@ -743,19 +1015,61 @@ components: items: type: array items: - $ref: "#/components/schemas/Point3D" + $ref: '#/components/schemas/Point3D' + Source: type: string title: Source Region: type: string title: Region + BoundingBox: + type: array + description: 'Bounding box array that contains [minX,minY,maxX,maxY]' + example: '[-74.382527,40.477003,-73.322346,40.916383]' + items: + type: number + minLength: 4 + maxLength: 4 + WGS84Circle: + type: object + properties: + lat: + type: number + lon: + type: number + radius: + type: number + UTMCircle: + type: object + properties: + x: + type: number + y: + type: number + zone: + type: number + minimum: 1 + maximum: 60 + radius: + type: number + geo_context: + anyOf: + - type: object + properties: + bbox: + $ref: '#/components/schemas/BoundingBox' + - $ref: '#/components/schemas/WGS84Circle' + - $ref: '#/components/schemas/UTMCircle' + disable_fuzziness: + type: boolean + default: false securitySchemes: - X-API-Key: - type: "apiKey" - name: "X-API-Key" - in: "header" - X-User-ID: - type: "apiKey" - name: "X-User-ID" - in: "header" + x-api-key: + type: 'apiKey' + name: 'x-api-key' + in: 'header' + x-user-id: + type: 'apiKey' + name: 'x-user-id' + in: 'header' diff --git a/package-lock.json b/package-lock.json index a0ae8830..5eb0f3f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,19 @@ { "name": "geocoding", - "version": "0.5.0", + "version": "0.1.0-prealpha.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "geocoding", - "version": "0.5.0", + "version": "0.1.0-prealpha.4", "hasInstallScript": true, "license": "ISC", "dependencies": { + "@aws-sdk/client-s3": "^3.637.0", "@elastic/elasticsearch": "^8.13.1", "@godaddy/terminus": "^4.12.1", + "@map-colonies/cleanup-registry": "^1.1.0", "@map-colonies/error-express-handler": "^2.1.0", "@map-colonies/express-access-log-middleware": "^2.0.1", "@map-colonies/js-logger": "^1.0.1", @@ -22,6 +24,8 @@ "@opentelemetry/api-metrics": "0.23.0", "@opentelemetry/instrumentation-express": "0.32.1", "@opentelemetry/instrumentation-http": "0.35.1", + "@smithy/node-http-handler": "^3.1.4", + "ajv": "^8.17.1", "axios": "^0.21.1", "compression": "^1.7.4", "config": "^3.3.9", @@ -30,14 +34,15 @@ "fast-xml-parser": "^4.4.0", "geojson-validation": "^1.0.2", "http-status-codes": "^2.2.0", + "ioredis": "^5.4.1", "mgrs": "^2.0.0", "node-cron": "^3.0.3", "node-fetch-commonjs": "^3.3.2", - "pg": "^8.11.5", "proj4": "^2.11.0", + "redis": "^4.7.0", "reflect-metadata": "^0.1.13", "tsyringe": "^4.8.0", - "typeorm": "^0.3.20", + "utm-latlng": "^1.0.8", "wellknown": "^0.5.0" }, "devDependencies": { @@ -103,6 +108,1051 @@ "js-yaml": "^4.1.0" } }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/crc32c/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/util/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-s3/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/client-sso/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/client-sts": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/core": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "dependencies": { + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.637.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", + "dependencies": { + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sts": "^3.621.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", + "dependencies": { + "@aws-sdk/core": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-sso-oidc": "^3.614.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/types/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "dependencies": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/util-user-agent-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, "node_modules/@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -2107,28 +3157,6 @@ "node": ">=v14" } }, - "node_modules/@commitlint/config-validator/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "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/@commitlint/config-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/@commitlint/ensure": { "version": "17.4.4", "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-17.4.4.tgz", @@ -2619,7 +3647,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -2631,7 +3659,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2727,6 +3755,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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/@eslint/eslintrc/node_modules/globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", @@ -2742,6 +3787,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -2771,6 +3823,28 @@ "ajv": "^6.12.6" } }, + "node_modules/@fastify/ajv-compiler/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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/@fastify/ajv-compiler/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -2953,10 +4027,16 @@ "node": ">=6.9.0" } }, + "node_modules/@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -2973,6 +4053,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, "engines": { "node": ">=12" }, @@ -2984,6 +4065,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, "engines": { "node": ">=12" }, @@ -2994,12 +4076,14 @@ "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -3016,6 +4100,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -3030,6 +4115,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -3749,7 +4835,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6.0.0" } @@ -3767,7 +4853,7 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.17", @@ -3784,6 +4870,15 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "node_modules/@map-colonies/cleanup-registry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@map-colonies/cleanup-registry/-/cleanup-registry-1.1.0.tgz", + "integrity": "sha512-/lhIGklWPZSY37JwzhFJEtBlqwXDRhHSeCBwpPaGMxpycpt5ZRIVQxUt6Og4mt6c5GoRoX9dZYHY0qV3UMGvtQ==", + "dependencies": { + "nanoid": "^3.3.4", + "tiny-typed-emitter": "^2.1.0" + } + }, "node_modules/@map-colonies/error-express-handler": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@map-colonies/error-express-handler/-/error-express-handler-2.1.0.tgz", @@ -6223,6 +7318,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "optional": true, "engines": { "node": ">=14" @@ -6282,6 +7378,59 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "dependencies": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, + "node_modules/@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "peerDependencies": { + "@redis/client": "^1.0.0" + } + }, "node_modules/@redocly/ajv": { "version": "8.6.4", "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", @@ -6298,12 +7447,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@redocly/ajv/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/@redocly/openapi-cli": { "version": "1.0.0-beta.95", "resolved": "https://registry.npmjs.org/@redocly/openapi-cli/-/openapi-cli-1.0.0-beta.95.tgz", @@ -6461,34 +7604,914 @@ "@sinonjs/commons": "^2.0.0" } }, - "node_modules/@sqltools/formatter": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", - "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + "node_modules/@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/abort-controller/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/chunked-blob-reader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", + "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", + "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "dependencies": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/chunked-blob-reader-native/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/chunked-blob-reader/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/config-resolver": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/config-resolver/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", + "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/eventstream-codec": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/eventstream-codec/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", + "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", + "dependencies": { + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/eventstream-serde-universal": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", + "dependencies": { + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-universal/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "dependencies": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/fetch-http-handler/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "dependencies": { + "@smithy/chunked-blob-reader": "^3.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/hash-blob-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/hash-node": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/hash-stream-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/hash-stream-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/invalid-dependency": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/invalid-dependency/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/is-array-buffer/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/md5-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/md5-js/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/middleware-content-length": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", + "dependencies": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-content-length/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "dependencies": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/middleware-retry": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-retry/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-serde/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-stack/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/node-config-provider": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", + "dependencies": { + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/node-config-provider/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "dependencies": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/node-http-handler/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/property-provider": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/property-provider/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/protocol-http/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "dependencies": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/querystring-builder/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/querystring-parser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/service-error-classification": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", + "dependencies": { + "@smithy/types": "^3.3.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/signature-v4/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "dependencies": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/smithy-client/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/types/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "dependencies": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/url-parser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-base64/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@smithy/util-body-length-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-body-length-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-body-length-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-buffer-from/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-config-provider": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-config-provider/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", + "dependencies": { + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", + "dependencies": { + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-endpoints": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "dependencies": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-hex-encoding/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-middleware/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-retry": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", + "dependencies": { + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-retry/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "dependencies": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-stream/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-uri-escape/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-utf8/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/@smithy/util-waiter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", + "dependencies": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/util-waiter/node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true + "dev": true }, "node_modules/@tsconfig/node16": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "devOptional": true + "dev": true }, "node_modules/@types/accepts": { "version": "1.3.5", @@ -7393,7 +9416,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "devOptional": true, + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -7414,7 +9437,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.4.0" } @@ -7426,14 +9449,15 @@ "dev": true }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "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" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -7456,26 +9480,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "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/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -7523,11 +9527,6 @@ "node": ">=4" } }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" - }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -7541,14 +9540,6 @@ "node": ">= 8" } }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -7563,7 +9554,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "dev": true }, "node_modules/argparse": { "version": "2.0.1", @@ -8132,6 +10123,11 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -8435,107 +10431,6 @@ "node": ">=8" } }, - "node_modules/cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "dependencies": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "bin": { - "highlight": "bin/highlight" - }, - "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" - } - }, - "node_modules/cli-highlight/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cli-highlight/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/cli-highlight/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cli-highlight/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/cli-highlight/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-highlight/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-highlight/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "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/cli-spinners": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", @@ -8576,6 +10471,14 @@ "node": ">=0.8" } }, + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -9495,12 +11398,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true + "dev": true }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -9553,11 +11457,6 @@ "node": "*" } }, - "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" - }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -9671,6 +11570,14 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -9729,7 +11636,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.3.1" } @@ -9779,17 +11686,6 @@ "node": ">=8" } }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/dotgitignore": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", @@ -9863,7 +11759,8 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true }, "node_modules/ee-first": { "version": "1.1.1", @@ -10495,6 +12392,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -10645,6 +12559,13 @@ "node": ">=8" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -10959,21 +12880,6 @@ "path-to-regexp": "^6.2.0" } }, - "node_modules/express-openapi-validator/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "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/express-openapi-validator/node_modules/ajv-draft-04": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", @@ -10987,11 +12893,6 @@ } } }, - "node_modules/express-openapi-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/express-openapi-validator/node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -11095,6 +12996,28 @@ "node": ">= 10.0.0" } }, + "node_modules/fast-json-stringify/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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/fast-json-stringify/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", @@ -11114,10 +13037,16 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "node_modules/fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", + "license": "MIT" + }, "node_modules/fast-xml-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", - "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "funding": [ { "type": "github", @@ -11400,6 +13329,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -11415,6 +13345,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true, "engines": { "node": ">=14" }, @@ -12068,14 +13999,6 @@ "node": ">=8" } }, - "node_modules/highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", - "engines": { - "node": "*" - } - }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -12389,6 +14312,29 @@ "node": ">= 0.4" } }, + "node_modules/ioredis": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "dependencies": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ioredis" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -12747,7 +14693,8 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", @@ -12849,6 +14796,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -14747,9 +16695,10 @@ "dev": true }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -14881,26 +16830,6 @@ "set-cookie-parser": "^2.4.1" } }, - "node_modules/light-my-request/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "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/light-my-request/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "node_modules/light-my-request/node_modules/process-warning": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", @@ -14983,11 +16912,21 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -15220,7 +17159,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true + "dev": true }, "node_modules/makeerror": { "version": "1.0.12", @@ -15426,6 +17365,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", + "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -15508,14 +17448,21 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, "node_modules/natural-compare": { @@ -15873,28 +17820,6 @@ "openapi-types": "^9.3.1" } }, - "node_modules/openapi-response-validator/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "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/openapi-response-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/openapi-schema-validator": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/openapi-schema-validator/-/openapi-schema-validator-9.3.1.tgz", @@ -15907,28 +17832,6 @@ "openapi-types": "^9.3.1" } }, - "node_modules/openapi-schema-validator/node_modules/ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "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/openapi-schema-validator/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/openapi-types": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-9.3.1.tgz", @@ -16162,24 +18065,6 @@ "node": ">=0.10.0" } }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "dependencies": { - "parse5": "^6.0.1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -16210,6 +18095,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "engines": { "node": ">=8" } @@ -16233,6 +18119,7 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -16248,6 +18135,7 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true, "engines": { "node": "14 || >=16.14" } @@ -16266,43 +18154,6 @@ "node": ">=8" } }, - "node_modules/pg": { - "version": "8.11.5", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz", - "integrity": "sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==", - "dependencies": { - "pg-connection-string": "^2.6.4", - "pg-pool": "^3.6.2", - "pg-protocol": "^1.6.1", - "pg-types": "^2.1.0", - "pgpass": "1.x" - }, - "engines": { - "node": ">= 8.0.0" - }, - "optionalDependencies": { - "pg-cloudflare": "^1.1.1" - }, - "peerDependencies": { - "pg-native": ">=3.0.1" - }, - "peerDependenciesMeta": { - "pg-native": { - "optional": true - } - } - }, - "node_modules/pg-cloudflare": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", - "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", - "optional": true - }, - "node_modules/pg-connection-string": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", - "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" - }, "node_modules/pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", @@ -16311,14 +18162,6 @@ "node": ">=4.0.0" } }, - "node_modules/pg-pool": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", - "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", - "peerDependencies": { - "pg": ">=8.0" - } - }, "node_modules/pg-protocol": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", @@ -16339,14 +18182,6 @@ "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/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -17231,6 +19066,38 @@ "node": ">=8" } }, + "node_modules/redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "dependencies": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -17723,22 +19590,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -17750,6 +19606,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "engines": { "node": ">=8" } @@ -17926,6 +19783,11 @@ "node": ">=8" } }, + "node_modules/standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, "node_modules/standard-version": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz", @@ -18113,6 +19975,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -18191,6 +20054,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -18383,25 +20247,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/thread-stream": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz", @@ -18433,6 +20278,11 @@ "node": ">=6" } }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -18563,7 +20413,7 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "devOptional": true, + "dev": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -18712,316 +20562,11 @@ "integrity": "sha1-nIRAPyMjrlOZFnJ1SXY46h0vJEA=", "dev": true }, - "node_modules/typeorm": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", - "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", - "dependencies": { - "@sqltools/formatter": "^1.2.5", - "app-root-path": "^3.1.0", - "buffer": "^6.0.3", - "chalk": "^4.1.2", - "cli-highlight": "^2.1.11", - "dayjs": "^1.11.9", - "debug": "^4.3.4", - "dotenv": "^16.0.3", - "glob": "^10.3.10", - "mkdirp": "^2.1.3", - "reflect-metadata": "^0.2.1", - "sha.js": "^2.4.11", - "tslib": "^2.5.0", - "uuid": "^9.0.0", - "yargs": "^17.6.2" - }, - "bin": { - "typeorm": "cli.js", - "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", - "typeorm-ts-node-esm": "cli-ts-node-esm.js" - }, - "engines": { - "node": ">=16.13.0" - }, - "funding": { - "url": "https://opencollective.com/typeorm" - }, - "peerDependencies": { - "@google-cloud/spanner": "^5.18.0", - "@sap/hana-client": "^2.12.25", - "better-sqlite3": "^7.1.2 || ^8.0.0 || ^9.0.0", - "hdb-pool": "^0.1.6", - "ioredis": "^5.0.4", - "mongodb": "^5.8.0", - "mssql": "^9.1.1 || ^10.0.1", - "mysql2": "^2.2.5 || ^3.0.1", - "oracledb": "^6.3.0", - "pg": "^8.5.1", - "pg-native": "^3.0.0", - "pg-query-stream": "^4.0.0", - "redis": "^3.1.1 || ^4.0.0", - "sql.js": "^1.4.0", - "sqlite3": "^5.0.3", - "ts-node": "^10.7.0", - "typeorm-aurora-data-api-driver": "^2.0.0" - }, - "peerDependenciesMeta": { - "@google-cloud/spanner": { - "optional": true - }, - "@sap/hana-client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "hdb-pool": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mssql": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "oracledb": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-native": { - "optional": true - }, - "pg-query-stream": { - "optional": true - }, - "redis": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "ts-node": { - "optional": true - }, - "typeorm-aurora-data-api-driver": { - "optional": true - } - } - }, - "node_modules/typeorm/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/typeorm/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/typeorm/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/typeorm/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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/typeorm/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/typeorm/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/typeorm/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/typeorm/node_modules/glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/typeorm/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/typeorm/node_modules/mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==", - "bin": { - "mkdirp": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" - }, - "node_modules/typeorm/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/typeorm/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/typeorm/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/typeorm/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "engines": { - "node": ">=12" - } - }, "node_modules/typescript": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", - "devOptional": true, + "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -19164,6 +20709,11 @@ "node": ">= 0.4.0" } }, + "node_modules/utm-latlng": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/utm-latlng/-/utm-latlng-1.0.8.tgz", + "integrity": "sha512-jQ5l1BNTzNgas7jXOIh0jU0m5PU5LtmjYvRiLwR4iymYYAhEhQ6N0MGCnLjv5iO958b9tLMMSmzdbIIhLtXqCg==" + }, "node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -19180,7 +20730,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true + "dev": true }, "node_modules/v8-to-istanbul": { "version": "9.1.0", @@ -19308,6 +20858,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -19375,6 +20926,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -19391,6 +20943,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -19405,6 +20958,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -19415,7 +20969,8 @@ "node_modules/wrap-ansi-cjs/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "4.3.0", @@ -19561,7 +21116,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "dev": true, "engines": { "node": ">=6" } @@ -19601,6 +21156,983 @@ "js-yaml": "^4.1.0" } }, + "@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "requires": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "requires": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/client-s3": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", + "requires": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/client-sso": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/client-sso-oidc": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/client-sts": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/core": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "requires": { + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-http": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", + "requires": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", + "requires": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", + "requires": { + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-bucket-endpoint": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-expect-continue": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-flexible-checksums": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", + "requires": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-location-constraint": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-sdk-s3": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", + "requires": { + "@aws-sdk/core": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-ssec": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/signature-v4-multi-region": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", + "requires": { + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-arn-parser": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/xml-builder": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, "@babel/code-frame": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", @@ -20999,26 +23531,6 @@ "requires": { "@commitlint/types": "^17.4.4", "ajv": "^8.11.0" - }, - "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, "@commitlint/ensure": { @@ -21384,7 +23896,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "requires": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -21393,7 +23905,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -21474,6 +23986,18 @@ "strip-json-comments": "^3.1.1" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "globals": { "version": "13.20.0", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", @@ -21483,6 +24007,12 @@ "type-fest": "^0.20.2" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -21503,6 +24033,24 @@ "integrity": "sha512-gvCOUNpXsWrIQ3A4aXCLIdblL0tDq42BG/2Xw7oxbil9h11uow10ztS2GuFazNBfjbrsZ5nl+nPl5jDSjj5TSg==", "requires": { "ajv": "^6.12.6" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } } }, "@fastify/busboy": { @@ -21658,10 +24206,16 @@ "integrity": "sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==", "dev": true }, + "@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, "requires": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -21674,22 +24228,26 @@ "ansi-regex": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==" + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true }, "ansi-styles": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==" + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true }, "string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, "requires": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -21700,6 +24258,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, "requires": { "ansi-regex": "^6.0.1" } @@ -21708,6 +24267,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, "requires": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -22257,7 +24817,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "devOptional": true + "dev": true }, "@jridgewell/set-array": { "version": "1.1.1", @@ -22269,7 +24829,7 @@ "version": "1.4.14", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true + "dev": true }, "@jridgewell/trace-mapping": { "version": "0.3.17", @@ -22286,6 +24846,15 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, + "@map-colonies/cleanup-registry": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@map-colonies/cleanup-registry/-/cleanup-registry-1.1.0.tgz", + "integrity": "sha512-/lhIGklWPZSY37JwzhFJEtBlqwXDRhHSeCBwpPaGMxpycpt5ZRIVQxUt6Og4mt6c5GoRoX9dZYHY0qV3UMGvtQ==", + "requires": { + "nanoid": "^3.3.4", + "tiny-typed-emitter": "^2.1.0" + } + }, "@map-colonies/error-express-handler": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@map-colonies/error-express-handler/-/error-express-handler-2.1.0.tgz", @@ -24021,6 +26590,7 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, "optional": true }, "@protobufjs/aspromise": { @@ -24077,6 +26647,46 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==", + "requires": {} + }, + "@redis/client": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz", + "integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==", + "requires": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + } + }, + "@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==", + "requires": {} + }, + "@redis/json": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz", + "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==", + "requires": {} + }, + "@redis/search": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz", + "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==", + "requires": {} + }, + "@redis/time-series": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz", + "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==", + "requires": {} + }, "@redocly/ajv": { "version": "8.6.4", "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.6.4.tgz", @@ -24087,14 +26697,6 @@ "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" - }, - "dependencies": { - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, "@redocly/openapi-cli": { @@ -24234,34 +26836,889 @@ "@sinonjs/commons": "^2.0.0" } }, - "@sqltools/formatter": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", - "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==" + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/chunked-blob-reader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", + "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/chunked-blob-reader-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", + "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "requires": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/config-resolver": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", + "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/credential-provider-imds": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-codec": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", + "requires": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-serde-browser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", + "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", + "requires": { + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-serde-config-resolver": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-serde-node": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", + "requires": { + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-serde-universal": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", + "requires": { + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/hash-blob-browser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "requires": { + "@smithy/chunked-blob-reader": "^3.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/hash-node": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/hash-stream-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/invalid-dependency": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/md5-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/middleware-content-length": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/middleware-retry": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/node-config-provider": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", + "requires": { + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/property-provider": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/service-error-classification": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", + "requires": { + "@smithy/types": "^3.3.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-body-length-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-body-length-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-config-provider": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-defaults-mode-browser": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", + "requires": { + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-defaults-mode-node": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", + "requires": { + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-endpoints": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-retry": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", + "requires": { + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-waiter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "devOptional": true + "dev": true }, "@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true + "dev": true }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true + "dev": true }, "@tsconfig/node16": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "devOptional": true + "dev": true }, "@types/accepts": { "version": "1.3.5", @@ -25018,7 +28475,7 @@ "version": "8.8.2", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "devOptional": true + "dev": true }, "acorn-jsx": { "version": "5.3.2", @@ -25031,7 +28488,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "devOptional": true + "dev": true }, "add-stream": { "version": "1.0.0", @@ -25040,14 +28497,14 @@ "dev": true }, "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" } }, "ajv-formats": { @@ -25056,24 +28513,6 @@ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "requires": { "ajv": "^8.0.0" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - } } }, "ansi-escapes": { @@ -25107,11 +28546,6 @@ "color-convert": "^1.9.0" } }, - "any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" - }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -25122,11 +28556,6 @@ "picomatch": "^2.0.4" } }, - "app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==" - }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -25141,7 +28570,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true + "dev": true }, "argparse": { "version": "2.0.1", @@ -25591,6 +29020,11 @@ } } }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -25791,78 +29225,6 @@ "restore-cursor": "^3.1.0" } }, - "cli-highlight": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", - "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", - "requires": { - "chalk": "^4.0.0", - "highlight.js": "^10.7.1", - "mz": "^2.4.0", - "parse5": "^5.1.1", - "parse5-htmlparser2-tree-adapter": "^6.0.0", - "yargs": "^16.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "requires": { - "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" - } - } - } - }, "cli-spinners": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", @@ -25891,6 +29253,11 @@ "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true }, + "cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" + }, "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -26644,12 +30011,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true + "dev": true }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -26688,11 +30056,6 @@ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==" }, - "dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" - }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -26773,6 +30136,11 @@ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true }, + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -26815,7 +30183,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true + "dev": true }, "diff-sequences": { "version": "29.4.3", @@ -26850,11 +30218,6 @@ "is-obj": "^2.0.0" } }, - "dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" - }, "dotgitignore": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/dotgitignore/-/dotgitignore-2.1.0.tgz", @@ -26915,7 +30278,8 @@ "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true }, "ee-first": { "version": "1.1.1", @@ -27080,6 +30444,18 @@ "text-table": "^0.2.0" }, "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -27179,6 +30555,12 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -27742,28 +31124,12 @@ "path-to-regexp": "^6.2.0" }, "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, "ajv-draft-04": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", "requires": {} }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -27834,6 +31200,24 @@ "deepmerge": "^4.2.2", "rfdc": "^1.2.0", "string-similarity": "^4.0.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + } } }, "fast-levenshtein": { @@ -27852,10 +31236,15 @@ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, + "fast-uri": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + }, "fast-xml-parser": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.0.tgz", - "integrity": "sha512-kLY3jFlwIYwBNDojclKsNAC12sfD6NwW74QB2CoNGPvtVxjliYehVunB3HYyNi+n4Tt1dAcgwYvmKF/Z18flqg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", "requires": { "strnum": "^1.0.5" } @@ -28081,6 +31470,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dev": true, "requires": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -28089,7 +31479,8 @@ "signal-exit": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", - "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==" + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true } } }, @@ -28575,11 +31966,6 @@ "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", "dev": true }, - "highlight.js": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", - "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" - }, "homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -28802,6 +32188,22 @@ "side-channel": "^1.0.4" } }, + "ioredis": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", + "integrity": "sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==", + "requires": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + } + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -29037,7 +32439,8 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "istanbul-lib-coverage": { "version": "3.2.0", @@ -29119,6 +32522,7 @@ "version": "2.3.6", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dev": true, "requires": { "@isaacs/cliui": "^8.0.2", "@pkgjs/parseargs": "^0.11.0" @@ -30534,9 +33938,9 @@ "dev": true }, "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -30639,22 +34043,6 @@ "set-cookie-parser": "^2.4.1" }, "dependencies": { - "ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" - }, "process-warning": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", @@ -30729,11 +34117,21 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", @@ -30925,7 +34323,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true + "dev": true }, "makeerror": { "version": "1.0.12", @@ -31075,7 +34473,8 @@ "minipass": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", - "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==" + "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", + "dev": true }, "mkdirp": { "version": "0.5.6", @@ -31142,15 +34541,10 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, - "mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "requires": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } + "nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "natural-compare": { "version": "1.4.0", @@ -31417,26 +34811,6 @@ "requires": { "ajv": "^8.4.0", "openapi-types": "^9.3.1" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, "openapi-schema-validator": { @@ -31449,26 +34823,6 @@ "ajv-formats": "^2.0.2", "lodash.merge": "^4.6.1", "openapi-types": "^9.3.1" - }, - "dependencies": { - "ajv": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", - "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - } - }, - "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - } } }, "openapi-types": { @@ -31648,26 +35002,6 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, - "parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" - }, - "parse5-htmlparser2-tree-adapter": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", - "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", - "requires": { - "parse5": "^6.0.1" - }, - "dependencies": { - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - } - } - }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -31688,7 +35022,8 @@ "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true }, "path-parse": { "version": "1.0.7", @@ -31709,6 +35044,7 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dev": true, "requires": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -31717,7 +35053,8 @@ "lru-cache": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", - "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==" + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "dev": true } } }, @@ -31732,41 +35069,11 @@ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true }, - "pg": { - "version": "8.11.5", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.5.tgz", - "integrity": "sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw==", - "requires": { - "pg-cloudflare": "^1.1.1", - "pg-connection-string": "^2.6.4", - "pg-pool": "^3.6.2", - "pg-protocol": "^1.6.1", - "pg-types": "^2.1.0", - "pgpass": "1.x" - } - }, - "pg-cloudflare": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", - "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", - "optional": true - }, - "pg-connection-string": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", - "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" - }, "pg-int8": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" }, - "pg-pool": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", - "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", - "requires": {} - }, "pg-protocol": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", @@ -31784,14 +35091,6 @@ "postgres-interval": "^1.1.0" } }, - "pgpass": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", - "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", - "requires": { - "split2": "^4.1.0" - } - }, "picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -32456,6 +35755,32 @@ "strip-indent": "^3.0.0" } }, + "redis": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz", + "integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==", + "requires": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.6.0", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.7", + "@redis/search": "1.2.0", + "@redis/time-series": "1.1.0" + } + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "requires": { + "redis-errors": "^1.0.0" + } + }, "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", @@ -32824,19 +36149,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, - "sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "requires": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -32844,7 +36161,8 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true }, "shimmer": { "version": "1.2.1", @@ -32985,6 +36303,11 @@ } } }, + "standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, "standard-version": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/standard-version/-/standard-version-9.5.0.tgz", @@ -33124,6 +36447,7 @@ "version": "npm:string-width@4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -33186,6 +36510,7 @@ "version": "npm:strip-ansi@6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -33326,22 +36651,6 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, - "thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "requires": { - "any-promise": "^1.0.0" - } - }, - "thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "requires": { - "thenify": ">= 3.1.0 < 4" - } - }, "thread-stream": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz", @@ -33370,6 +36679,11 @@ "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-8.0.2.tgz", "integrity": "sha512-ApGvZ6vVvTNdsmt676grvCkUCGwzG9IqXma5Z07xJgiC5L7akUMof5U8G2JTI9Rz/ovtVhJBlY6mNhEvtjzOIg==" }, + "tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -33452,7 +36766,7 @@ "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", - "devOptional": true, + "dev": true, "requires": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -33554,164 +36868,11 @@ "integrity": "sha1-nIRAPyMjrlOZFnJ1SXY46h0vJEA=", "dev": true }, - "typeorm": { - "version": "0.3.20", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", - "integrity": "sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==", - "requires": { - "@sqltools/formatter": "^1.2.5", - "app-root-path": "^3.1.0", - "buffer": "^6.0.3", - "chalk": "^4.1.2", - "cli-highlight": "^2.1.11", - "dayjs": "^1.11.9", - "debug": "^4.3.4", - "dotenv": "^16.0.3", - "glob": "^10.3.10", - "mkdirp": "^2.1.3", - "reflect-metadata": "^0.2.1", - "sha.js": "^2.4.11", - "tslib": "^2.5.0", - "uuid": "^9.0.0", - "yargs": "^17.6.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "requires": { - "balanced-match": "^1.0.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "glob": { - "version": "10.3.12", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", - "requires": { - "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.10.2" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "minipass": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", - "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==" - }, - "mkdirp": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.6.tgz", - "integrity": "sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==" - }, - "reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==" - } - } - }, "typescript": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.3.tgz", "integrity": "sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==", - "devOptional": true + "dev": true }, "uglify-js": { "version": "3.15.5", @@ -33810,6 +36971,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "utm-latlng": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/utm-latlng/-/utm-latlng-1.0.8.tgz", + "integrity": "sha512-jQ5l1BNTzNgas7jXOIh0jU0m5PU5LtmjYvRiLwR4iymYYAhEhQ6N0MGCnLjv5iO958b9tLMMSmzdbIIhLtXqCg==" + }, "uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -33819,7 +36985,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true + "dev": true }, "v8-to-istanbul": { "version": "9.1.0", @@ -33934,6 +37100,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -34005,6 +37172,7 @@ "version": "npm:wrap-ansi@7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -34015,6 +37183,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -34023,6 +37192,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -34030,7 +37200,8 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true } } }, @@ -34115,7 +37286,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true + "dev": true }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 0d74c749..81a0a0bd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "geocoding", - "version": "0.5.0", + "version": "0.1.0-prealpha.4", "description": "Geocoding service for MapColonies", "main": "./src/index.ts", "scripts": { @@ -23,11 +23,7 @@ "assets:copy": "copyfiles -f ./config/* ./dist/config && copyfiles -f ./openapi3.yaml ./dist/ && copyfiles ./package.json dist", "clean": "rimraf dist", "install": "npx husky install", - "dev:scripts": "npx ts-node ./devScripts/index.ts", - "typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js -d dataSource.ts", - "migration:create": "npm run typeorm migration:generate", - "migration:run": "npm run typeorm migration:run", - "migration:revert": "npm run typeorm migration:revert" + "dev:scripts": "npx ts-node ./devScripts/index.ts" }, "directories": { "test": "tests" @@ -46,8 +42,10 @@ } }, "dependencies": { + "@aws-sdk/client-s3": "^3.637.0", "@elastic/elasticsearch": "^8.13.1", "@godaddy/terminus": "^4.12.1", + "@map-colonies/cleanup-registry": "^1.1.0", "@map-colonies/error-express-handler": "^2.1.0", "@map-colonies/express-access-log-middleware": "^2.0.1", "@map-colonies/js-logger": "^1.0.1", @@ -58,6 +56,8 @@ "@opentelemetry/api-metrics": "0.23.0", "@opentelemetry/instrumentation-express": "0.32.1", "@opentelemetry/instrumentation-http": "0.35.1", + "@smithy/node-http-handler": "^3.1.4", + "ajv": "^8.17.1", "axios": "^0.21.1", "compression": "^1.7.4", "config": "^3.3.9", @@ -66,14 +66,15 @@ "fast-xml-parser": "^4.4.0", "geojson-validation": "^1.0.2", "http-status-codes": "^2.2.0", + "ioredis": "^5.4.1", "mgrs": "^2.0.0", "node-cron": "^3.0.3", "node-fetch-commonjs": "^3.3.2", - "pg": "^8.11.5", "proj4": "^2.11.0", + "redis": "^4.7.0", "reflect-metadata": "^0.1.13", "tsyringe": "^4.8.0", - "typeorm": "^0.3.20", + "utm-latlng": "^1.0.8", "wellknown": "^0.5.0" }, "devDependencies": { diff --git a/src/app.ts b/src/app.ts index 805d3dfc..57da10f1 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,12 +1,10 @@ import { Application } from 'express'; import { DependencyContainer } from 'tsyringe'; -import { RegisterOptions, registerExternalValues } from './containerConfig'; +import { registerExternalValues, RegisterOptions } from './containerConfig'; import { ServerBuilder } from './serverBuilder'; -async function getApp(registerOptions?: RegisterOptions): Promise<{ app: Application; container: DependencyContainer }> { +export const getApp = async (registerOptions?: RegisterOptions): Promise<{ app: Application; container: DependencyContainer }> => { const container = await registerExternalValues(registerOptions); const app = container.resolve(ServerBuilder).build(); return { app, container }; -} - -export { getApp }; +}; diff --git a/src/common/constants.ts b/src/common/constants.ts index f5cf88cd..ab5493a1 100644 --- a/src/common/constants.ts +++ b/src/common/constants.ts @@ -14,21 +14,14 @@ export const SERVICES: Record = { METER: Symbol('Meter'), APPLICATION: Symbol('Application'), ELASTIC_CLIENTS: Symbol('ElasticClients'), + REDIS: Symbol('Redis'), + S3_CLIENT: Symbol('S3Client'), + CLEANUP_REGISTRY: Symbol('CleanupRegistry'), }; /* eslint-enable @typescript-eslint/naming-convention */ export const ON_SIGNAL = Symbol('onSignal'); export const HEALTHCHECK = Symbol('healthcheck'); -export const CONTROL_FIELDS = [ - 'type', - 'geometry', - 'properties.OBJECT_COMMAND_NAME', - 'properties.TILE_NAME', - 'properties.TYPE', - 'properties.ENTITY_HEB', - 'properties.SUB_TILE_ID', - 'properties.SECTION', -]; - +export const siteConfig = 'application.site'; export const elasticConfigPath = 'db.elastic'; diff --git a/src/common/elastic/index.ts b/src/common/elastic/index.ts index 51645492..da80c831 100644 --- a/src/common/elastic/index.ts +++ b/src/common/elastic/index.ts @@ -19,7 +19,7 @@ const initElasticsearchClient = (clientOptions: ClientOptions): ElasticClient => return client; }; -export const elasticClientFactory: FactoryFunction = (container: DependencyContainer): ElasticClients => { +export const elasticClientsFactory: FactoryFunction = (container: DependencyContainer): ElasticClients => { const config = container.resolve(SERVICES.CONFIG); const logger = container.resolve(SERVICES.LOGGER); diff --git a/src/common/elastic/utils.ts b/src/common/elastic/utils.ts index 5e7e6278..da798cba 100644 --- a/src/common/elastic/utils.ts +++ b/src/common/elastic/utils.ts @@ -1,24 +1,13 @@ import { estypes } from '@elastic/elasticsearch'; -import { IConfig, WGS84Coordinate } from '../interfaces'; -import { InternalServerError } from '../errors'; -import { elasticConfigPath, CONTROL_FIELDS } from '../constants'; -import { ElasticDbClientsConfig } from './interfaces'; -import { ElasticClient, ElasticClients } from './index'; - -export const getElasticClientQuerySize = (config: IConfig, key: keyof ElasticClients): number => { - return config.get(elasticConfigPath)[key].properties.defaultResponseLimit; -}; - -/* eslint-disable @typescript-eslint/naming-convention */ -export const additionalControlSearchProperties = (config: IConfig, size: number): { size: number; index: string; _source: string[] } => ({ - size, - index: config.get(elasticConfigPath).control.properties.index as string, - _source: CONTROL_FIELDS, -}); -/* eslint-enable @typescript-eslint/naming-convention */ +import { BBox } from 'geojson'; +import { WGS84Coordinate } from '../interfaces'; +import { ServiceUnavailableError } from '../errors'; +import { ElasticClient } from './index'; export const queryElastic = async (client: ElasticClient, body: estypes.SearchRequest): Promise> => { - const clientNotAvailableError = new InternalServerError('Elasticsearch client is not available'); + const clientNotAvailableError = new ServiceUnavailableError( + 'Elasticsearch client is not available. As for, the search request cannot be executed.' + ); try { if (!(await client.ping())) { throw clientNotAvailableError; @@ -32,7 +21,7 @@ export const queryElastic = async (client: ElasticClient, body: estypes.Searc /* eslint-disable @typescript-eslint/naming-convention */ export const boundingBox = ( - bbox: number[] + bbox: BBox ): { geo_bounding_box: { geometry: { diff --git a/src/common/errors.ts b/src/common/errors.ts index c93dc054..f23ed0cd 100644 --- a/src/common/errors.ts +++ b/src/common/errors.ts @@ -26,6 +26,14 @@ export class InternalServerError extends Error implements HttpError { } } +export class ServiceUnavailableError extends Error implements HttpError { + public readonly status = httpStatus.SERVICE_UNAVAILABLE; + + public constructor(message: string) { + super(message); + } +} + export class NotFoundError extends Error implements HttpError { public readonly status = httpStatus.NOT_FOUND; @@ -33,3 +41,19 @@ export class NotFoundError extends Error implements HttpError { super(message); } } + +export class NotImplementedError extends Error implements HttpError { + public readonly status = httpStatus.NOT_IMPLEMENTED; + + public constructor(message: string) { + super(message); + } +} + +export class TimeoutError extends Error implements HttpError { + public readonly status = httpStatus.REQUEST_TIMEOUT; + + public constructor(message: string) { + super(message); + } +} diff --git a/src/common/interfaces.ts b/src/common/interfaces.ts index cadc78ee..861f2aec 100644 --- a/src/common/interfaces.ts +++ b/src/common/interfaces.ts @@ -1,6 +1,6 @@ -import { Client, ClientOptions } from '@elastic/elasticsearch'; -import { DataSourceOptions } from 'typeorm'; -import { Feature, FeatureCollection as GeoJSONFeatureCollection } from 'geojson'; +import { estypes } from '@elastic/elasticsearch'; +import { BBox, Feature, FeatureCollection as GeoJSONFeatureCollection, GeoJsonProperties } from 'geojson'; +import { RemoveUnderscore } from './utils'; export interface IConfig { get: (setting: string) => T; @@ -14,21 +14,14 @@ export interface OpenApiConfig { uiPath: string; } -export type PostgresDbConfig = { - enableSslAuth: boolean; - sslPaths: { ca: string; cert: string; key: string }; -} & DataSourceOptions; - export interface GeoContext { - bbox?: number[]; + bbox?: BBox; radius?: number; lon?: number; lat?: number; -} - -export interface Geometry { - type: string; - coordinates: number[][][]; + x?: number; + y?: number; + zone?: number; } export interface FeatureCollection extends GeoJSONFeatureCollection { @@ -60,4 +53,58 @@ export interface IApplication { }; nameTranslationsKeys: string[]; mainLanguageRegex: string; + controlObjectDisplayNamePrefixes: { + [key: string]: string; + }; +} + +export enum GeoContextMode { + FILTER = 'filter', + BIAS = 'bias', +} + +export interface FeebackApiGeocodingResponse { + userId: string; + apiKey: string; + site: string; + response: JSON; + respondedAt: Date; +} + +/* eslint-disable @typescript-eslint/naming-convention */ +export interface CommonRequestParameters { + geo_context?: GeoContext; + geo_context_mode?: GeoContextMode; + limit: number; + disable_fuzziness: boolean; +} +/* eslint-enable @typescript-eslint/naming-convention */ + +export interface GenericGeocodingResponse extends FeatureCollection { + geocoding: { + version?: string; + query: G & CommonRequestParameters; + response: Pick & { + /* eslint-disable @typescript-eslint/naming-convention */ + results_count?: estypes.SearchHitsMetadata['total']; + match_latency_ms?: estypes.SearchResponse['took']; + /* eslint-enable @typescript-eslint/naming-convention */ + } & { [key: string]: unknown }; + }; + features: (T & { + properties: RemoveUnderscore, '_score'>> & + GeoJsonProperties & { + matches: { + layer: string; + source: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + source_id: string[]; + }[]; + names: { + [key: string]: string | string[] | undefined; + display: string; + default: string[]; + }; + }; + })[]; } diff --git a/src/common/middlewares/feedbackApi.middleware.ts b/src/common/middlewares/feedbackApi.middleware.ts new file mode 100644 index 00000000..c44db663 --- /dev/null +++ b/src/common/middlewares/feedbackApi.middleware.ts @@ -0,0 +1,59 @@ +import * as crypto from 'node:crypto'; +import { Request, Response, NextFunction } from 'express'; +import { Logger } from '@map-colonies/js-logger'; +import { inject, injectable } from 'tsyringe'; +import { SERVICES, siteConfig } from '../constants'; +import { RedisClient } from '../redis'; +import { FeebackApiGeocodingResponse, IConfig } from '../interfaces'; +import { XApi } from './utils'; + +const REDIS_TTL = 300; + +@injectable() +export class FeedbackApiMiddlewareManager { + public constructor( + @inject(SERVICES.LOGGER) private readonly logger: Logger, + @inject(SERVICES.REDIS) private readonly redis: RedisClient, + @inject(SERVICES.CONFIG) private readonly config: IConfig + ) {} + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + public saveResponses = (req: Request, res: Response, next: NextFunction) => { + const reqId = res.getHeader(XApi.REQUEST); + const redisClient = this.redis; + const logger = this.logger; + + const drSite = this.config.get(siteConfig); + + logger.info({ msg: 'saving response to redis' }); + const geocodingResponseDetails: FeebackApiGeocodingResponse = { + userId: req.headers[XApi.USER] as string, + apiKey: req.headers[XApi.KEY] as string, + site: drSite, + response: JSON.parse('{}') as JSON, + respondedAt: new Date(), + }; + + const originalJson = res.json; + const logJson = function (this: Response, body: JSON): Response { + geocodingResponseDetails.response = body; + redisClient + .setEx(reqId as string, REDIS_TTL, JSON.stringify(geocodingResponseDetails)) + .then(() => { + logger.info({ msg: `response ${reqId?.toString() ?? ''} saved to redis` }); + }) + .catch((error) => logger.error('Error setting key:', error)); + + return originalJson.call(this, body); + }; + res.json = logJson; + next(); + }; + + // eslint-disable-next-line @typescript-eslint/explicit-function-return-type + public setRequestId = (req: Request, res: Response, next: NextFunction) => { + const reqId = crypto.randomUUID(); + res.append(XApi.REQUEST, reqId); + next(); + }; +} diff --git a/src/common/middlewares/utils.ts b/src/common/middlewares/utils.ts new file mode 100644 index 00000000..31b6288c --- /dev/null +++ b/src/common/middlewares/utils.ts @@ -0,0 +1,5 @@ +export enum XApi { + REQUEST = 'x-request-id', + USER = 'x-user-id', + KEY = 'x-api-key', +} diff --git a/src/common/postgresql/index.ts b/src/common/postgresql/index.ts deleted file mode 100644 index 23f4c835..00000000 --- a/src/common/postgresql/index.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { readFileSync } from 'fs'; -import { HealthCheck } from '@godaddy/terminus'; -import { DataSource, DataSourceOptions, QueryFailedError } from 'typeorm'; -import { PostgresDbConfig } from '../interfaces'; -import { LatLon } from '../../latLon/DAL/latLon'; -import { promiseTimeout } from './promiseTimeout'; - -let connectionSingleton: DataSource | undefined; - -const DB_TIMEOUT = 5000; - -enum TransactionFailure { - SERIALIZATION_FAILURE = '40001', - DEADLOCK_DETECTED = '40P01', -} - -interface QueryFailedErrorWithCode extends QueryFailedError { - code: string | undefined; -} - -export enum TransactionName { - TRY_CLOSING_FILE = 'TryClosingFile', - CREATE_RERUN = 'CreateRerun', - TRY_CLOSING_CHANGESET = 'TryClosingChangeset', - TRY_CLOSING_CHANGESETS = 'TryClosingChangesets', -} - -export const isTransactionFailure = (error: unknown): boolean => { - if (error instanceof QueryFailedError) { - const code = (error as QueryFailedErrorWithCode).code; - return code === TransactionFailure.SERIALIZATION_FAILURE || code === TransactionFailure.DEADLOCK_DETECTED; - } - return false; -}; - -export const DB_ENTITIES = [LatLon]; - -export const createConnectionOptions = (dbConfig: PostgresDbConfig): DataSourceOptions => { - const { enableSslAuth, sslPaths, ...connectionOptions } = dbConfig; - if (enableSslAuth && connectionOptions.type === 'postgres') { - connectionOptions.password = undefined; - connectionOptions.ssl = { key: readFileSync(sslPaths.key), cert: readFileSync(sslPaths.cert), ca: readFileSync(sslPaths.ca) }; - } - return { entities: [...DB_ENTITIES, '**/DAL/*.js'], ...connectionOptions }; -}; - -export const initDataSource = async (dbConfig: PostgresDbConfig): Promise => { - if (connectionSingleton === undefined || !connectionSingleton.isInitialized) { - connectionSingleton = new DataSource(createConnectionOptions(dbConfig)); - await connectionSingleton.initialize(); - } - return connectionSingleton; -}; - -export const getDbHealthCheckFunction = (connection: DataSource): HealthCheck => { - return async (): Promise => { - const check = connection.query('SELECT 1').then(() => { - return; - }); - return promiseTimeout(DB_TIMEOUT, check); - }; -}; - -export interface ReturningId { - id: string; -} - -export type ReturningResult = [T[], number]; diff --git a/src/common/postgresql/promiseTimeout.ts b/src/common/postgresql/promiseTimeout.ts deleted file mode 100644 index 7f6255c4..00000000 --- a/src/common/postgresql/promiseTimeout.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class TimeoutError extends Error {} - -export const promiseTimeout = async (ms: number, promise: Promise): Promise => { - // Create a promise that rejects in milliseconds - const timeout = new Promise((_, reject) => { - const id = setTimeout(() => { - clearTimeout(id); - reject(new TimeoutError(`Timed out in + ${ms} + ms.`)); - }, ms); - }); - - // Returns a race between our timeout and the passed in promise - return Promise.race([promise, timeout]); -}; diff --git a/src/common/projections.ts b/src/common/projections.ts index f36a7501..71824891 100644 --- a/src/common/projections.ts +++ b/src/common/projections.ts @@ -1,3 +1,4 @@ +//TODO: REMOVE THIS FILE - if proj4 is not in use? export const wgs84Projection = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs'; export const utmProjection = (zone: number): string => `+proj=utm +zone=${zone} +ellps=WGS84 +datum=WGS84 +units=m +no_defs`; diff --git a/src/common/redis/index.ts b/src/common/redis/index.ts new file mode 100644 index 00000000..2642959c --- /dev/null +++ b/src/common/redis/index.ts @@ -0,0 +1,50 @@ +import { readFileSync } from 'fs'; +import { Logger } from '@map-colonies/js-logger'; +import { createClient, RedisClientOptions } from 'redis'; +import { DependencyContainer, FactoryFunction } from 'tsyringe'; +import { SERVICES } from '../constants'; +import { IConfig } from '../interfaces'; +import { RedisConfig } from './interfaces'; + +const DEFAULT_LIMIT_FROM = 0; +const DEFAULT_LIMIT_SIZE = 1000; + +const createConnectionOptions = (redisConfig: RedisConfig): Partial => { + const { host, port, enableSslAuth, sslPaths, ...clientOptions } = redisConfig; + clientOptions.socket = { host, port }; + if (enableSslAuth) { + clientOptions.socket = { + ...clientOptions.socket, + tls: true, + key: sslPaths.key !== '' ? readFileSync(sslPaths.key) : undefined, + cert: sslPaths.cert !== '' ? readFileSync(sslPaths.cert) : undefined, + ca: sslPaths.ca !== '' ? readFileSync(sslPaths.ca) : undefined, + }; + } + + return clientOptions; +}; + +export const CONNECTION_TIMEOUT = 5000; + +export const DEFAULT_LIMIT = { from: DEFAULT_LIMIT_FROM, size: DEFAULT_LIMIT_SIZE }; + +export type RedisClient = ReturnType; + +export const redisClientFactory: FactoryFunction = (container: DependencyContainer): RedisClient | undefined => { + const logger = container.resolve(SERVICES.LOGGER); + const config = container.resolve(SERVICES.CONFIG); + const dbConfig = config.get('db.redis'); + const connectionOptions = createConnectionOptions(dbConfig); + try { + const redisClient = createClient(connectionOptions) + // .on('error', (error: Error) => logger.error({ msg: 'redis client errored', err: error })) + // .on('reconnecting', (...args) => logger.warn({ msg: 'redis client reconnecting', ...args })) + .on('end', (...args) => logger.info({ msg: 'redis client end', ...args })) + .on('connect', (...args) => logger.debug({ msg: 'redis client connected', ...args })) + .on('ready', (...args) => logger.debug({ msg: 'redis client is ready', ...args })); + return redisClient; + } catch (error) { + logger.error('Connection to Redis was unsuccessful', error); + } +}; diff --git a/src/common/redis/interfaces.ts b/src/common/redis/interfaces.ts new file mode 100644 index 00000000..145a202b --- /dev/null +++ b/src/common/redis/interfaces.ts @@ -0,0 +1,8 @@ +import { RedisClientOptions } from 'redis'; + +export type RedisConfig = { + host: string; + port: number; + enableSslAuth: boolean; + sslPaths: { ca: string; cert: string; key: string }; +} & RedisClientOptions; diff --git a/src/common/s3/index.ts b/src/common/s3/index.ts new file mode 100644 index 00000000..4b145662 --- /dev/null +++ b/src/common/s3/index.ts @@ -0,0 +1,48 @@ +import * as https from 'https'; +import * as http from 'http'; +import { S3Client, S3ClientConfig } from '@aws-sdk/client-s3'; +import { NodeHttpHandler } from '@smithy/node-http-handler'; +import { Logger } from '@map-colonies/js-logger'; +import { DependencyContainer, FactoryFunction } from 'tsyringe'; +import { IConfig } from '../interfaces'; +import { SERVICES } from '../constants'; + +const createConnectionOptions = (clientOptions: S3ClientConfig): S3ClientConfig => ({ + ...clientOptions, + forcePathStyle: true, + requestHandler: new NodeHttpHandler({ + httpsAgent: new https.Agent({ + rejectUnauthorized: false, + }), + httpAgent: new http.Agent(), + }), +}); + +const initS3Client = (clientOptions: S3ClientConfig): S3Client => { + const client = new S3Client(createConnectionOptions(clientOptions)); + return client; +}; + +export const s3ClientFactory: FactoryFunction = (container: DependencyContainer): S3Client => { + const config = container.resolve(SERVICES.CONFIG); + const logger = container.resolve(SERVICES.LOGGER); + + const s3Config = config.get(s3ConfigPath); + + const client = initS3Client(s3Config); + logger.info(`S3 Client is initialized`); + + return client; +}; + +export type S3Config = S3ClientConfig & { bucketName: string } & { + files: { + [key: string]: + | { + bucket: string; + fileName: string; + } + | undefined; + }; +}; +export const s3ConfigPath = 'db.s3'; diff --git a/src/common/s3/s3Repository.ts b/src/common/s3/s3Repository.ts new file mode 100644 index 00000000..71e24ede --- /dev/null +++ b/src/common/s3/s3Repository.ts @@ -0,0 +1,69 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import fs from 'fs'; +import path from 'path'; +import { Logger } from '@map-colonies/js-logger'; +import { FactoryFunction } from 'tsyringe'; +import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { SERVICES } from '../constants'; +import { IConfig } from '../interfaces'; +import { S3Config, s3ConfigPath } from '.'; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const createS3Repository = (s3Client: S3Client, config: IConfig, logger: Logger) => { + return { + async downloadFile(key: keyof S3Config['files']): Promise { + try { + const fileData = config.get(s3ConfigPath).files[key]; + if (!fileData) { + throw new Error(`${key} data is missing in the configuration`); + } + const { bucket: Bucket, fileName: Key } = fileData; + logger.info(`Downloading ${Key} file from S3`); + + const command = new GetObjectCommand({ + Bucket, + Key, + }); + + const { Body } = await s3Client.send(command); + + const filePath = path.join(__dirname, 'downloads', Key); + + try { + await fs.promises.mkdir(path.dirname(filePath), { recursive: true }); + } catch { + //folder already exists + } + + await new Promise((resolve, reject) => { + (Body as NodeJS.ReadableStream) + .pipe(fs.createWriteStream(filePath)) + .on('error', (err: Error) => { + reject(err); + }) + .on('close', () => { + logger.info('table.json file was downloaded successfully'); + resolve(); + }); + }); + + return filePath; + } catch (error) { + logger.error(`Error while downloading ${key}'s data from S3. Error: ${(error as Error).message}`); + throw error; + } + }, + }; +}; + +export const s3RepositoryFactory: FactoryFunction = (depContainer) => { + return createS3Repository( + depContainer.resolve(SERVICES.S3_CLIENT), + depContainer.resolve(SERVICES.CONFIG), + depContainer.resolve(SERVICES.LOGGER) + ); +}; + +export type S3Repository = ReturnType; + +export const S3_REPOSITORY_SYMBOL = Symbol('S3_REPOSITORY_SYMBOL'); diff --git a/src/common/utils.ts b/src/common/utils.ts index 11359d34..94f9873d 100644 --- a/src/common/utils.ts +++ b/src/common/utils.ts @@ -1,32 +1,61 @@ +import * as Ajv from 'ajv'; +import utm from 'utm-latlng'; +import { BBox, Geometry, Point } from 'geojson'; import { estypes } from '@elastic/elasticsearch'; -import proj4 from 'proj4'; -import { Item } from '../item/models/item'; -import { Tile } from '../tile/models/tile'; -import { Route } from '../route/models/route'; -import { elasticConfigPath } from './constants'; -import { utmProjection, wgs84Projection } from './projections'; -import { FeatureCollection, IConfig, WGS84Coordinate } from './interfaces'; +import { ListBucketsCommand, S3Client } from '@aws-sdk/client-s3'; +import { DependencyContainer, FactoryFunction } from 'tsyringe'; +import { Logger } from '@map-colonies/js-logger'; +import { ELASTIC_KEYWORDS } from '../control/constants'; +import { TimeoutError } from './errors'; +import { RedisClient } from './redis'; +import { GeoContext, GeoContextMode, WGS84Coordinate } from './interfaces'; +import { SERVICES } from './constants'; import { ElasticClients } from './elastic'; -import { ElasticDbClientsConfig } from './elastic/interfaces'; - -export const formatResponse = (elasticResponse: estypes.SearchResponse): FeatureCollection => ({ - type: 'FeatureCollection', - features: [ - ...(elasticResponse.hits.hits.map((item) => { - const source = item._source; - if (source?.properties) { - Object.keys(source.properties).forEach((key) => { - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (source.properties !== null && source.properties[key as keyof typeof source.properties] == null) { - delete source.properties[key as keyof typeof source.properties]; - } - }); - } - return source; - }) as T[]), - ], +import { BadRequestError } from './errors'; + +type SnakeToCamelCase = S extends `${infer T}_${infer U}` ? `${T}${Capitalize>}` : S; + +type CamelToSnakeCase = S extends `${infer T}${infer U}` + ? U extends Uncapitalize + ? `${Lowercase}${CamelToSnakeCase}` + : `${Lowercase}_${CamelToSnakeCase>}` + : S; + +const ajv = new Ajv.Ajv(); + +const parsePoint = (split: string[] | number[]): Geometry => ({ + type: 'Point', + coordinates: split.map(Number), }); +const parseBbox = (split: [string, string, string, string] | BBox): Geometry => { + const [xMin, yMin, xMax, yMax] = split.map(Number); + return { + type: 'Polygon', + coordinates: [ + [ + [xMin, yMin], + [xMin, yMax], + [xMax, yMax], + [xMax, yMin], + [xMin, yMin], + ], + ], + }; +}; + +export type ConvertSnakeToCamelCase = { + [K in keyof T as SnakeToCamelCase]: T[K]; +}; + +export type ConvertCamelToSnakeCase = { + [K in keyof T as CamelToSnakeCase]: T[K]; +}; + +export type RemoveUnderscore = { + [K in keyof T as K extends `_${infer Rest}` ? Rest : K]: T[K]; +}; + export const validateWGS84Coordinate = (coordinate: { lon: number; lat: number }): boolean => { // eslint-disable-next-line @typescript-eslint/no-magic-numbers const [min, max] = [0, 180]; @@ -49,8 +78,7 @@ export const validateWGS84Coordinate = (coordinate: { lon: number; lat: number } /* eslint-disable @typescript-eslint/naming-convention */ export const convertWgs84ToUTM = ( - latitude: number, - longitude: number, + { longitude, latitude }: { longitude: number; latitude: number }, utmPrecision = 0 ): | string @@ -59,33 +87,197 @@ export const convertWgs84ToUTM = ( Northing: number; ZoneNumber: number; } => { - const zone = Math.floor((longitude + 180) / 6) + 1; - - const [easting, northing] = proj4(wgs84Projection, utmProjection(zone), [longitude, latitude]); - - return { - Easting: +easting.toFixed(utmPrecision), - Northing: +northing.toFixed(utmPrecision), - ZoneNumber: zone, + //@ts-expect-error: utm has problem with types. Need to ignore ts error here + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + const UTMcoordinates = new utm().convertLatLngToUtm(latitude, longitude, utmPrecision) as { + Easting: number; + Northing: number; + ZoneNumber: number; + ZoneLetter: string; }; + + return UTMcoordinates; }; /* eslint-enable @typescript-eslint/naming-convention */ export const convertUTMToWgs84 = (x: number, y: number, zone: number): WGS84Coordinate => { - const [longitude, latitude] = proj4(utmProjection(zone), wgs84Projection, [x, y]); - return { lat: latitude, lon: longitude }; + //TODO: change ZONE Letter to relevent letter. Currently it is hardcoded to 'N' + const { lat, lng: lon } = new utm().convertUtmToLatLng(x, y, zone, 'N') as { lat: number; lng: number }; + return { lat, lon }; }; -export const validateTile = (tile: { tileName: string; subTileNumber: number[] }): boolean => { - if (!tile.tileName || !Array.isArray(tile.subTileNumber) || tile.subTileNumber.length !== 3) { - return false; +export const healthCheckFactory: FactoryFunction = (container: DependencyContainer): void => { + const logger = container.resolve(SERVICES.LOGGER); + const elasticClients = container.resolve(SERVICES.ELASTIC_CLIENTS); + const s3Client = container.resolve(SERVICES.S3_CLIENT); + const redis = container.resolve(SERVICES.REDIS); + + for (const [key, client] of Object.entries(elasticClients)) { + client.cluster + .health({}) + .then(() => { + return; + }) + .catch((error) => { + logger.error(`Healthcheck failed for ${key}. Error: ${(error as Error).message}`); + }); } - //regex = /^-?d+$/; - const regex = /^(\d\d)$/; - for (const subTileNumber of tile.subTileNumber) { - if (!regex.test(`${subTileNumber}`)) { - return false; - } + + s3Client + .send(new ListBucketsCommand({})) + .then(() => { + return; + }) + .catch((error) => { + logger.error(`Healthcheck failed for S3. Error: ${(error as Error).message}`); + }); + + redis + .ping() + .then(() => { + return; + }) + .catch((error) => { + logger.error(`Healthcheck failed for Redis. Error: ${(error as Error).message}`); + }); +}; + +export const promiseTimeout = async (ms: number, promise: Promise): Promise => { + // create a promise that rejects in milliseconds + const timeout = new Promise((_, reject) => { + const id = setTimeout(() => { + clearTimeout(id); + reject(new TimeoutError(`Timed out in + ${ms} + ms.`)); + }, ms); + }); + + // returns a race between our timeout and the passed in promise + return Promise.race([promise, timeout]); +}; + +export const parseGeo = (input: GeoContext): Geometry | undefined => { + //TODO: Add geojson validation + //TODO: refactor this function + if (input.bbox !== undefined) { + return parseBbox(input.bbox); + } else if ( + (input.x !== undefined && input.y !== undefined && input.zone !== undefined && input.zone !== undefined) || + (input.lon !== undefined && input.lat !== undefined) + ) { + const { x, y, zone, radius } = input; + const { lon, lat } = x && y && zone ? convertUTMToWgs84(x, y, zone) : (input as Required>); + + return { type: 'Circle', coordinates: (parsePoint([lon, lat]) as Point).coordinates, radius: `${radius ?? ''}` } as unknown as Geometry; + } +}; + +export const validateGeoContext = (geoContext: GeoContext): boolean => { + const geoCOntextSchema: Ajv.Schema = { + oneOf: [ + { + type: 'object', + properties: { + bbox: { + oneOf: [ + { + type: 'array', + minItems: 4, + maxItems: 4, + items: { + type: 'number', + }, + }, + { + type: 'array', + minItems: 6, + maxItems: 6, + items: { + type: 'number', + }, + }, + ], + }, + }, + required: ['bbox'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + lat: { + type: 'number', + }, + lon: { + type: 'number', + }, + radius: { + type: 'number', + }, + }, + required: ['lat', 'lon', 'radius'], + additionalProperties: false, + }, + { + type: 'object', + properties: { + x: { + type: 'number', + }, + y: { + type: 'number', + }, + zone: { + type: 'number', + }, + radius: { + type: 'number', + }, + }, + required: ['x', 'y', 'zone', 'radius'], + additionalProperties: false, + }, + ], + }; + + const validate = ajv.compile(geoCOntextSchema); + const isValid = validate(geoContext); + const messagePrefix = 'geo_context validation: '; + + if (!isValid) { + throw new BadRequestError( + messagePrefix + + 'geo_context must contain one of the following: {"bbox": [number,number,number,number] | [number,number,number,number,number,number]}, {"lat": number, "lon": number, "radius": number}, or {"x": number, "y": number, "zone": number, "radius": number}' + ); } + return true; }; + +export const geoContextQuery = ( + geoContext?: GeoContext, + geoContextMode?: GeoContextMode, + elasticGeoShapeField = ELASTIC_KEYWORDS.geometry +): { [key in 'filter' | 'should']?: estypes.QueryDslQueryContainer[] } => { + if (geoContext === undefined && geoContextMode === undefined) { + return {}; + } + if ((geoContext !== undefined && geoContextMode === undefined) || (geoContext === undefined && geoContextMode !== undefined)) { + throw new BadRequestError('/control/utils/geoContextQuery: geo_context and geo_context_mode must be both defined or both undefined'); + } + + validateGeoContext(geoContext!); + + return { + [geoContextMode === GeoContextMode.FILTER ? 'filter' : 'should']: [ + { + // eslint-disable-next-line @typescript-eslint/naming-convention + geo_shape: { + [elasticGeoShapeField]: { + shape: parseGeo(geoContext!), + }, + boost: geoContextMode === GeoContextMode.BIAS ? 1.1 : 1, //TODO: change magic number + }, + }, + ], + }; +}; diff --git a/src/containerConfig.ts b/src/containerConfig.ts index e3b4d6e5..a365a4b5 100644 --- a/src/containerConfig.ts +++ b/src/containerConfig.ts @@ -1,28 +1,33 @@ import config from 'config'; import { getOtelMixin } from '@map-colonies/telemetry'; -import { DataSource } from 'typeorm'; +import { ListBucketsCommand, S3Client } from '@aws-sdk/client-s3'; import { instancePerContainerCachingFactory } from 'tsyringe'; import { trace, metrics as OtelMetrics } from '@opentelemetry/api'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; import { DependencyContainer } from 'tsyringe/dist/typings/types'; import jsLogger, { LoggerOptions } from '@map-colonies/js-logger'; import { Metrics } from '@map-colonies/telemetry'; -import { SERVICES, SERVICE_NAME } from './common/constants'; +import { ScheduledTask } from 'node-cron'; +import { HEALTHCHECK, ON_SIGNAL, SERVICES, SERVICE_NAME } from './common/constants'; import { tracing } from './common/tracing'; import { InjectionObject, registerDependencies } from './common/dependencyRegistration'; -import { elasticClientFactory, ElasticClients } from './common/elastic'; -import { IApplication, PostgresDbConfig } from './common/interfaces'; -import { TILE_REPOSITORY_SYMBOL, tileRepositoryFactory } from './tile/DAL/tileRepository'; -import { TILE_ROUTER_SYMBOL, tileRouterFactory } from './tile/routes/tileRouter'; -import { ITEM_REPOSITORY_SYMBOL, itemRepositoryFactory } from './item/DAL/itemRepository'; -import { ITEM_ROUTER_SYMBOL, itemRouterFactory } from './item/routes/itemRouter'; -import { ROUTE_REPOSITORY_SYMBOL, routeRepositoryFactory } from './route/DAL/routeRepository'; -import { ROUTE_ROUTER_SYMBOL, routeRouterFactory } from './route/routes/routeRouter'; -import { initDataSource } from './common/postgresql'; -import { LATLON_CUSTOM_REPOSITORY_SYMBOL, latLonRepositoryFactory } from './latLon/DAL/latLonRepository'; +import { elasticClientsFactory, ElasticClients } from './common/elastic'; +import { IApplication } from './common/interfaces'; +import { TILE_REPOSITORY_SYMBOL, tileRepositoryFactory } from './control/tile/DAL/tileRepository'; +import { TILE_ROUTER_SYMBOL, tileRouterFactory } from './control/tile/routes/tileRouter'; +import { ITEM_ROUTER_SYMBOL, itemRouterFactory } from './control/item/routes/itemRouter'; +import { ROUTE_REPOSITORY_SYMBOL, routeRepositoryFactory } from './control/route/DAL/routeRepository'; +import { ROUTE_ROUTER_SYMBOL, routeRouterFactory } from './control/route/routes/routeRouter'; import { LAT_LON_ROUTER_SYMBOL, latLonRouterFactory } from './latLon/routes/latLonRouter'; -import { GEOTEXT_REPOSITORY_SYMBOL, geotextRepositoryFactory } from './geotextSearch/DAL/geotextSearchRepository'; -import { GEOTEXT_SEARCH_ROUTER_SYMBOL, geotextSearchRouterFactory } from './geotextSearch/routes/geotextSearchRouter'; -import { cronLoadTileLatLonDataFactory, cronLoadTileLatLonDataSymbol } from './latLon/DAL/latLonDAL'; +import { GEOTEXT_REPOSITORY_SYMBOL, geotextRepositoryFactory } from './location/DAL/locationRepository'; +import { GEOTEXT_SEARCH_ROUTER_SYMBOL, geotextSearchRouterFactory } from './location/routes/locationRouter'; +import { cronLoadTileLatLonDataFactory, cronLoadTileLatLonDataSymbol, latLonDalSymbol, latLonSignletonFactory } from './latLon/DAL/latLonDAL'; +import { ITEM_REPOSITORY_SYMBOL, itemRepositoryFactory } from './control/item/DAL/itemRepository'; +import { RedisClient, redisClientFactory } from './common/redis'; +import { s3ClientFactory } from './common/s3'; +import { S3_REPOSITORY_SYMBOL, s3RepositoryFactory } from './common/s3/s3Repository'; +import { healthCheckFactory } from './common/utils'; +import { MGRS_ROUTER_SYMBOL, mgrsRouterFactory } from './mgrs/routers/mgrsRouter'; export interface RegisterOptions { override?: InjectionObject[]; @@ -30,72 +35,139 @@ export interface RegisterOptions { } export const registerExternalValues = async (options?: RegisterOptions): Promise => { - const loggerConfig = config.get('telemetry.logger'); - const logger = jsLogger({ ...loggerConfig, prettyPrint: loggerConfig.prettyPrint, mixin: getOtelMixin() }); + const cleanupRegistry = new CleanupRegistry(); + try { + const loggerConfig = config.get('telemetry.logger'); + const logger = jsLogger({ ...loggerConfig, prettyPrint: loggerConfig.prettyPrint, mixin: getOtelMixin() }); + const cleanupRegistryLogger = logger.child({ subComponent: 'cleanupRegistry' }); - const metrics = new Metrics(); - metrics.start(); + cleanupRegistry.on('itemFailed', (id, error, msg) => cleanupRegistryLogger.error({ msg, itemId: id, err: error })); + cleanupRegistry.on('finished', (status) => cleanupRegistryLogger.info({ msg: `cleanup registry finished cleanup`, status })); - tracing.start(); - const tracer = trace.getTracer(SERVICE_NAME); + const metrics = new Metrics(); + cleanupRegistry.register({ func: metrics.stop.bind(metrics), id: SERVICES.METER }); + metrics.start(); - const applicationConfig: IApplication = config.get('application'); + cleanupRegistry.register({ func: tracing.stop.bind(tracing), id: SERVICES.TRACER }); + tracing.start(); + const tracer = trace.getTracer(SERVICE_NAME); - const postgresqlDataSourceOptions = config.get('db.postgresql'); + const applicationConfig: IApplication = config.get('application'); - const postgresqlConnection = await initDataSource(postgresqlDataSourceOptions); - - const dependencies: InjectionObject[] = [ - { token: SERVICES.CONFIG, provider: { useValue: config } }, - { token: SERVICES.LOGGER, provider: { useValue: logger } }, - { token: SERVICES.TRACER, provider: { useValue: tracer } }, - { token: SERVICES.METER, provider: { useValue: OtelMetrics.getMeterProvider().getMeter(SERVICE_NAME) } }, - { token: SERVICES.APPLICATION, provider: { useValue: applicationConfig } }, - { - token: SERVICES.ELASTIC_CLIENTS, - provider: { useFactory: instancePerContainerCachingFactory(elasticClientFactory) }, - postInjectionHook: async (deps: DependencyContainer): Promise => { - const elasticClients = deps.resolve(SERVICES.ELASTIC_CLIENTS); - try { - const response = await Promise.all([elasticClients.control?.ping(), elasticClients.geotext?.ping()]); - response.forEach((res) => { - if (!res) { - logger.error('Failed to connect to Elasticsearch', res); - } + const dependencies: InjectionObject[] = [ + { token: SERVICES.CONFIG, provider: { useValue: config } }, + { token: SERVICES.LOGGER, provider: { useValue: logger } }, + { token: SERVICES.TRACER, provider: { useValue: tracer } }, + { token: SERVICES.METER, provider: { useValue: OtelMetrics.getMeterProvider().getMeter(SERVICE_NAME) } }, + { token: SERVICES.APPLICATION, provider: { useValue: applicationConfig } }, + { token: HEALTHCHECK, provider: { useFactory: healthCheckFactory } }, + { + token: ON_SIGNAL, + provider: { + useFactory: instancePerContainerCachingFactory((container) => { + const cleanupRegistry = container.resolve(SERVICES.CLEANUP_REGISTRY); + return cleanupRegistry.trigger.bind(cleanupRegistry); + }), + }, + }, + { + token: SERVICES.ELASTIC_CLIENTS, + provider: { useFactory: instancePerContainerCachingFactory(elasticClientsFactory) }, + postInjectionHook: async (deps: DependencyContainer): Promise => { + const elasticClients = deps.resolve(SERVICES.ELASTIC_CLIENTS); + try { + const response = await Promise.all([elasticClients.control?.ping(), elasticClients.geotext?.ping()]); + response.forEach((res) => { + if (!res) { + logger.error('Failed to connect to Elasticsearch', res); + } + }); + cleanupRegistry.register({ + func: async () => { + await elasticClients.control.close(); + await elasticClients.geotext.close(); + }, + id: SERVICES.ELASTIC_CLIENTS, + }); + } catch (err) { + logger.error('Failed to connect to Elasticsearch', err); + } + }, + }, + { + token: SERVICES.S3_CLIENT, + provider: { useFactory: s3ClientFactory }, + postInjectionHook: async (deps: DependencyContainer): Promise => { + const s3Client = deps.resolve(SERVICES.S3_CLIENT); + try { + await s3Client.send(new ListBucketsCommand({})); + logger.info('Connected to S3'); + } catch (err) { + logger.error('Failed to connect to S3', err); + } + cleanupRegistry.register({ + func: async () => { + s3Client.destroy(); + return Promise.resolve(); + }, + id: SERVICES.S3_CLIENT, }); - } catch (err) { - logger.error('Failed to connect to Elasticsearch', err); - } + }, }, - }, - { token: DataSource, provider: { useValue: postgresqlConnection } }, - { token: TILE_REPOSITORY_SYMBOL, provider: { useFactory: tileRepositoryFactory } }, - { token: TILE_ROUTER_SYMBOL, provider: { useFactory: tileRouterFactory } }, - { token: ITEM_REPOSITORY_SYMBOL, provider: { useFactory: itemRepositoryFactory } }, - { token: ITEM_ROUTER_SYMBOL, provider: { useFactory: itemRouterFactory } }, - { token: ROUTE_REPOSITORY_SYMBOL, provider: { useFactory: routeRepositoryFactory } }, - { token: ROUTE_ROUTER_SYMBOL, provider: { useFactory: routeRouterFactory } }, - { token: LATLON_CUSTOM_REPOSITORY_SYMBOL, provider: { useFactory: latLonRepositoryFactory } }, - { token: LAT_LON_ROUTER_SYMBOL, provider: { useFactory: latLonRouterFactory } }, - { token: GEOTEXT_REPOSITORY_SYMBOL, provider: { useFactory: geotextRepositoryFactory } }, - { token: GEOTEXT_SEARCH_ROUTER_SYMBOL, provider: { useFactory: geotextSearchRouterFactory } }, - { - token: cronLoadTileLatLonDataSymbol, - provider: { - useFactory: cronLoadTileLatLonDataFactory, + { + token: S3_REPOSITORY_SYMBOL, + provider: { useFactory: s3RepositoryFactory }, + }, + { + token: SERVICES.CLEANUP_REGISTRY, + provider: { useValue: cleanupRegistry }, + }, + { token: TILE_REPOSITORY_SYMBOL, provider: { useFactory: tileRepositoryFactory } }, + { token: TILE_ROUTER_SYMBOL, provider: { useFactory: tileRouterFactory } }, + { token: ITEM_REPOSITORY_SYMBOL, provider: { useFactory: itemRepositoryFactory } }, + { token: ITEM_ROUTER_SYMBOL, provider: { useFactory: itemRouterFactory } }, + { token: ROUTE_REPOSITORY_SYMBOL, provider: { useFactory: routeRepositoryFactory } }, + { token: ROUTE_ROUTER_SYMBOL, provider: { useFactory: routeRouterFactory } }, + { token: LAT_LON_ROUTER_SYMBOL, provider: { useFactory: latLonRouterFactory } }, + { token: GEOTEXT_REPOSITORY_SYMBOL, provider: { useFactory: geotextRepositoryFactory } }, + { token: GEOTEXT_SEARCH_ROUTER_SYMBOL, provider: { useFactory: geotextSearchRouterFactory } }, + { token: MGRS_ROUTER_SYMBOL, provider: { useFactory: mgrsRouterFactory } }, + { token: latLonDalSymbol, provider: { useFactory: latLonSignletonFactory } }, + { + token: cronLoadTileLatLonDataSymbol, + provider: { + useFactory: cronLoadTileLatLonDataFactory, + }, + postInjectionHook: async (deps: DependencyContainer): Promise => { + const cronLoadTileLatLonData = deps.resolve(cronLoadTileLatLonDataSymbol); + cronLoadTileLatLonData.start(); + cleanupRegistry.register({ + func: async () => { + cronLoadTileLatLonData.stop(); + return Promise.resolve(); + }, + id: cronLoadTileLatLonDataSymbol, + }); + }, }, - }, - { - token: 'onSignal', - provider: { - useValue: { - useValue: async (): Promise => { - await Promise.all([tracing.stop(), metrics.stop()]); - }, + { + token: SERVICES.REDIS, + provider: { useFactory: instancePerContainerCachingFactory(redisClientFactory) }, + postInjectionHook: async (deps: DependencyContainer): Promise => { + try { + const redis = deps.resolve(SERVICES.REDIS); + cleanupRegistry.register({ func: redis.disconnect.bind(redis), id: SERVICES.REDIS }); + await redis.connect(); + } catch (error) { + logger.error('Connection to redis failed', error); + } }, }, - }, - ]; - const container = await registerDependencies(dependencies, options?.override, options?.useChild); - return container; + ]; + const container = await registerDependencies(dependencies, options?.override, options?.useChild); + return container; + } catch (error) { + await cleanupRegistry.trigger(); + throw error; + } }; diff --git a/src/control/constants.ts b/src/control/constants.ts new file mode 100644 index 00000000..620d816e --- /dev/null +++ b/src/control/constants.ts @@ -0,0 +1,22 @@ +export const ELASTIC_KEYWORDS = { + type: 'properties.TYPE.keyword', + tileName: 'properties.TILE_NAME.keyword', + subTileId: 'properties.SUB_TILE_ID.keyword', + geometry: 'geometry', + objectCommandName: 'properties.OBJECT_COMMAND_NAME.keyword', + layerName: 'properties.LAYER_NAME.keyword', + tiedTo: 'properties.TIED_TO', +}; + +export const CONTROL_FIELDS = [ + 'type', + 'geometry', + 'properties.OBJECT_COMMAND_NAME', + 'properties.TILE_NAME', + 'properties.TYPE', + 'properties.ENTITY_HEB', + 'properties.SUB_TILE_ID', + 'properties.SECTION', + 'properties.TIED_TO', + 'properties.LAYER_NAME', +]; diff --git a/src/item/DAL/itemRepository.ts b/src/control/item/DAL/itemRepository.ts similarity index 78% rename from src/item/DAL/itemRepository.ts rename to src/control/item/DAL/itemRepository.ts index b7df1f41..b7070433 100644 --- a/src/item/DAL/itemRepository.ts +++ b/src/control/item/DAL/itemRepository.ts @@ -1,16 +1,15 @@ import { Logger } from '@map-colonies/js-logger'; import { estypes } from '@elastic/elasticsearch'; import { FactoryFunction } from 'tsyringe'; -import { IConfig } from '../../common/interfaces'; -import { ElasticClients } from '../../common/elastic'; -import { additionalControlSearchProperties, queryElastic } from '../../common/elastic/utils'; -import { ElasticClient } from '../../common/elastic'; +import { IConfig } from '../../../common/interfaces'; +import { ElasticClients } from '../../../common/elastic'; +import { queryElastic } from '../../../common/elastic/utils'; +import { ElasticClient } from '../../../common/elastic'; import { Item } from '../models/item'; -import { SERVICES } from '../../common/constants'; +import { SERVICES } from '../../../common/constants'; +import { additionalControlSearchProperties } from '../../utils'; import { ItemQueryParams, queryForItems } from './queries'; -/* eslint-enable @typescript-eslint/naming-convention */ - // eslint-disable-next-line @typescript-eslint/explicit-function-return-type const createItemRepository = (client: ElasticClient, config: IConfig, logger: Logger) => { return { diff --git a/src/control/item/DAL/queries.ts b/src/control/item/DAL/queries.ts new file mode 100644 index 00000000..9e612b70 --- /dev/null +++ b/src/control/item/DAL/queries.ts @@ -0,0 +1,59 @@ +import { estypes } from '@elastic/elasticsearch'; +import { CommonRequestParameters } from '../../../common/interfaces'; +import { ELASTIC_KEYWORDS } from '../../constants'; +import { ConvertSnakeToCamelCase, geoContextQuery } from '../../../common/utils'; + +export interface ItemQueryParams extends ConvertSnakeToCamelCase { + commandName: string; + tile?: string; + subTile?: string; +} +/* eslint-disable @typescript-eslint/naming-convention */ +export const queryForItems = ({ + commandName, + tile, + subTile, + geoContext, + geoContextMode, + disableFuzziness, +}: ItemQueryParams): estypes.SearchRequest => ({ + query: { + bool: { + must: [ + { + term: { + [ELASTIC_KEYWORDS.type]: 'ITEM', + }, + }, + { + match: { + [ELASTIC_KEYWORDS.objectCommandName]: { + query: commandName, + fuzziness: disableFuzziness ? undefined : 1, + prefix_length: 1, + }, + }, + }, + ...(tile ?? '' + ? [ + { + term: { + [ELASTIC_KEYWORDS.tileName]: tile, + }, + }, + ] + : []), + ...(subTile ?? '' + ? [ + { + term: { + [ELASTIC_KEYWORDS.subTileId]: subTile, + }, + }, + ] + : []), + ], + ...geoContextQuery(geoContext, geoContextMode), + }, + }, +}); diff --git a/src/item/controllers/itemController.ts b/src/control/item/controllers/itemController.ts similarity index 67% rename from src/item/controllers/itemController.ts rename to src/control/item/controllers/itemController.ts index 1a8f5776..1d76bd7a 100644 --- a/src/item/controllers/itemController.ts +++ b/src/control/item/controllers/itemController.ts @@ -4,20 +4,17 @@ import { BoundCounter, Meter } from '@opentelemetry/api-metrics'; import { RequestHandler } from 'express'; import httpStatus from 'http-status-codes'; import { injectable, inject } from 'tsyringe'; -import { SERVICES } from '../../common/constants'; +import { SERVICES } from '../../../common/constants'; import { ItemManager } from '../models/itemManager'; import { Item } from '../models/item'; -import { FeatureCollection, GeoContext } from '../../common/interfaces'; +import { CommonRequestParameters, FeatureCollection } from '../../../common/interfaces'; type GetItemsHandler = RequestHandler, undefined, GetItemsQueryParams>; -export interface GetItemsQueryParams { +export interface GetItemsQueryParams extends CommonRequestParameters { command_name: string; tile?: string; sub_tile?: string; - geo_context?: string; - reduce_fuzzy_match?: string; - size?: string; } @injectable() @@ -34,17 +31,16 @@ export class ItemController { public getItems: GetItemsHandler = async (req, res, next) => { try { - const { command_name: commandName, tile, sub_tile, geo_context, reduce_fuzzy_match, size } = req.query; - const response = await this.manager.getItems( - { - commandName, - tile, - subTile: sub_tile ? parseInt(sub_tile) : undefined, - geo: geo_context ? (JSON.parse(geo_context) as GeoContext) : undefined, - }, - reduce_fuzzy_match == 'true', - size ? parseInt(size) : undefined - ); + const { command_name: commandName, tile, sub_tile, geo_context, geo_context_mode, disable_fuzziness, limit } = req.query; + const response = await this.manager.getItems({ + tile, + subTile: sub_tile, + commandName, + geoContext: geo_context, + geoContextMode: geo_context_mode, + limit, + disableFuzziness: disable_fuzziness, + }); return res.status(httpStatus.OK).json(response); } catch (error: unknown) { this.logger.warn('itemController.getItems Error:', error); diff --git a/src/control/item/models/item.ts b/src/control/item/models/item.ts new file mode 100644 index 00000000..5b57db3a --- /dev/null +++ b/src/control/item/models/item.ts @@ -0,0 +1,11 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Feature, GeoJsonProperties } from 'geojson'; + +export interface Item extends Feature { + properties: GeoJsonProperties & { + TYPE: 'ITEM'; + OBJECT_COMMAND_NAME: string; + LAYER_NAME: string; + TIED_TO?: string; + }; +} diff --git a/src/control/item/models/itemManager.ts b/src/control/item/models/itemManager.ts new file mode 100644 index 00000000..b751e8a9 --- /dev/null +++ b/src/control/item/models/itemManager.ts @@ -0,0 +1,28 @@ +import { IConfig } from 'config'; +import { Logger } from '@map-colonies/js-logger'; +import { inject, injectable } from 'tsyringe'; +import { estypes } from '@elastic/elasticsearch'; +import { SERVICES } from '../../../common/constants'; +import { ITEM_REPOSITORY_SYMBOL, ItemRepository } from '../DAL/itemRepository'; +import { ItemQueryParams } from '../DAL/queries'; +import { formatResponse } from '../../utils'; +import { FeatureCollection, IApplication } from '../../../common/interfaces'; +import { Item } from './item'; + +@injectable() +export class ItemManager { + public constructor( + @inject(SERVICES.LOGGER) private readonly logger: Logger, + @inject(SERVICES.CONFIG) private readonly config: IConfig, + @inject(SERVICES.APPLICATION) private readonly application: IApplication, + @inject(ITEM_REPOSITORY_SYMBOL) private readonly itemRepository: ItemRepository + ) {} + + public async getItems(itemQueryParams: ItemQueryParams): Promise> { + const { limit } = itemQueryParams; + let elasticResponse: estypes.SearchResponse | undefined = undefined; + elasticResponse = await this.itemRepository.getItems(itemQueryParams, limit); + + return formatResponse(elasticResponse, itemQueryParams, this.application.controlObjectDisplayNamePrefixes); + } +} diff --git a/src/item/routes/itemRouter.ts b/src/control/item/routes/itemRouter.ts similarity index 100% rename from src/item/routes/itemRouter.ts rename to src/control/item/routes/itemRouter.ts diff --git a/src/control/route/DAL/queries.ts b/src/control/route/DAL/queries.ts new file mode 100644 index 00000000..b278ee23 --- /dev/null +++ b/src/control/route/DAL/queries.ts @@ -0,0 +1,78 @@ +import { estypes } from '@elastic/elasticsearch'; +import { CommonRequestParameters } from '../../../common/interfaces'; +import { ELASTIC_KEYWORDS } from '../../constants'; +import { ConvertSnakeToCamelCase, geoContextQuery } from '../../../common/utils'; + +export interface RouteQueryParams extends ConvertSnakeToCamelCase { + commandName: string; + controlPoint?: string; +} + +/* eslint-disable @typescript-eslint/naming-convention */ +export const queryForRoute = ({ geoContext, geoContextMode, commandName, disableFuzziness }: RouteQueryParams): estypes.SearchRequest => ({ + query: { + bool: { + must: [ + { + term: { + [ELASTIC_KEYWORDS.type]: 'ROUTE', + }, + }, + { + match: { + [ELASTIC_KEYWORDS.objectCommandName]: { + query: commandName, + fuzziness: disableFuzziness ? undefined : 1, + prefix_length: 1, + }, + }, + }, + ], + ...geoContextQuery(geoContext, geoContextMode), + }, + }, +}); + +export const queryForControlPointInRoute = ({ + controlPoint, + commandName, + geoContext, + geoContextMode, + disableFuzziness, +}: RouteQueryParams & Required>): estypes.SearchRequest => { + const geoContextOperation = geoContextQuery(geoContext, geoContextMode); + + const esQuery: estypes.SearchRequest = { + query: { + bool: { + must: [ + { + match: { + [ELASTIC_KEYWORDS.objectCommandName]: { + query: controlPoint, + fuzziness: disableFuzziness ? undefined : 1, + prefix_length: 1, + }, + }, + }, + ], + filter: [ + { + terms: { + [ELASTIC_KEYWORDS.layerName]: ['CONTROL_GIL_GDB.CTR_CONTROL_POINT_N', 'CONTROL_GIL_GDB.CTR_CONTROL_POINT_CROSS_N'], + }, + }, + { + term: { + [ELASTIC_KEYWORDS.tiedTo]: commandName, + }, + }, + ...(geoContextOperation.filter ?? []), + ], + should: geoContextOperation.should ?? [], + }, + }, + }; + + return esQuery; +}; diff --git a/src/route/DAL/routeRepository.ts b/src/control/route/DAL/routeRepository.ts similarity index 71% rename from src/route/DAL/routeRepository.ts rename to src/control/route/DAL/routeRepository.ts index 40c013a4..34da5b2e 100644 --- a/src/route/DAL/routeRepository.ts +++ b/src/control/route/DAL/routeRepository.ts @@ -1,17 +1,16 @@ -import { Logger } from '@map-colonies/js-logger'; import { estypes } from '@elastic/elasticsearch'; import { FactoryFunction } from 'tsyringe'; -import { ElasticClient } from '../../common/elastic'; -import { elasticConfigPath, SERVICES } from '../../common/constants'; +import { ElasticClient } from '../../../common/elastic'; +import { SERVICES } from '../../../common/constants'; import { Route } from '../models/route'; -import { IConfig } from '../../common/interfaces'; -import { ElasticClients } from '../../common/elastic'; -import { ElasticDbClientsConfig } from '../../common/elastic/interfaces'; -import { additionalControlSearchProperties, queryElastic } from '../../common/elastic/utils'; +import { IConfig } from '../../../common/interfaces'; +import { ElasticClients } from '../../../common/elastic'; +import { queryElastic } from '../../../common/elastic/utils'; +import { additionalControlSearchProperties } from '../../utils'; import { RouteQueryParams, queryForControlPointInRoute, queryForRoute } from './queries'; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const createRouteRepository = (client: ElasticClient, config: IConfig, logger: Logger) => { +const createRouteRepository = (client: ElasticClient, config: IConfig) => { return { async getRoutes(routeQueryParams: RouteQueryParams, size: number): Promise> { const response = await queryElastic(client, { ...additionalControlSearchProperties(config, size), ...queryForRoute(routeQueryParams) }); @@ -38,8 +37,7 @@ export type RouteRepository = ReturnType; export const routeRepositoryFactory: FactoryFunction = (depContainer) => { return createRouteRepository( depContainer.resolve(SERVICES.ELASTIC_CLIENTS).control, - depContainer.resolve(SERVICES.CONFIG), - depContainer.resolve(SERVICES.LOGGER) + depContainer.resolve(SERVICES.CONFIG) ); }; diff --git a/src/route/controllers/routeController.ts b/src/control/route/controllers/routeController.ts similarity index 67% rename from src/route/controllers/routeController.ts rename to src/control/route/controllers/routeController.ts index ce388fd3..87983fd3 100644 --- a/src/route/controllers/routeController.ts +++ b/src/control/route/controllers/routeController.ts @@ -4,19 +4,16 @@ import { BoundCounter, Meter } from '@opentelemetry/api-metrics'; import { RequestHandler } from 'express'; import httpStatus from 'http-status-codes'; import { injectable, inject } from 'tsyringe'; -import { SERVICES } from '../../common/constants'; +import { SERVICES } from '../../../common/constants'; import { RouteManager } from '../models/routeManager'; import { Route } from '../models/route'; -import { FeatureCollection, GeoContext } from '../../common/interfaces'; +import { CommonRequestParameters, FeatureCollection } from '../../../common/interfaces'; type GetRoutesHandler = RequestHandler, undefined, GetRoutesQueryParams>; -export interface GetRoutesQueryParams { +export interface GetRoutesQueryParams extends CommonRequestParameters { command_name: string; control_point?: string; - geo_context?: string; - reduce_fuzzy_match?: string; - size?: string; } @injectable() @@ -33,16 +30,15 @@ export class RouteController { public getRoutes: GetRoutesHandler = async (req, res, next) => { try { - const { command_name: commandName, control_point, geo_context, reduce_fuzzy_match, size } = req.query; - const response = await this.manager.getRoutes( - { - commandName, - controlPoint: control_point ? parseInt(control_point) : undefined, - geo: geo_context ? (JSON.parse(geo_context) as GeoContext) : undefined, - }, - reduce_fuzzy_match == 'true', - size ? parseInt(size) : undefined - ); + const { command_name: commandName, control_point, geo_context, geo_context_mode, disable_fuzziness, limit } = req.query; + const response = await this.manager.getRoutes({ + commandName, + controlPoint: control_point, + geoContext: geo_context, + geoContextMode: geo_context_mode, + disableFuzziness: disable_fuzziness, + limit, + }); return res.status(httpStatus.OK).json(response); } catch (error: unknown) { this.logger.warn('routeController.getRoutes Error:', error); diff --git a/src/control/route/models/route.ts b/src/control/route/models/route.ts new file mode 100644 index 00000000..607fcd12 --- /dev/null +++ b/src/control/route/models/route.ts @@ -0,0 +1,11 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Feature, GeoJsonProperties } from 'geojson'; + +export interface Route extends Feature { + properties: GeoJsonProperties & { + TYPE: 'ROUTE'; + OBJECT_COMMAND_NAME: string; + ENTITY_HEB: string; + LAYER_NAME: string; + }; +} diff --git a/src/control/route/models/routeManager.ts b/src/control/route/models/routeManager.ts new file mode 100644 index 00000000..ccc5934f --- /dev/null +++ b/src/control/route/models/routeManager.ts @@ -0,0 +1,36 @@ +import { IConfig } from 'config'; +import { Logger } from '@map-colonies/js-logger'; +import { inject, injectable } from 'tsyringe'; +import { estypes } from '@elastic/elasticsearch'; +import { SERVICES } from '../../../common/constants'; +import { ROUTE_REPOSITORY_SYMBOL, RouteRepository } from '../DAL/routeRepository'; +import { RouteQueryParams } from '../DAL/queries'; +import { formatResponse } from '../../utils'; +import { FeatureCollection, IApplication } from '../../../common/interfaces'; +import { Route } from './route'; + +@injectable() +export class RouteManager { + public constructor( + @inject(SERVICES.LOGGER) private readonly logger: Logger, + @inject(SERVICES.CONFIG) private readonly config: IConfig, + @inject(SERVICES.APPLICATION) private readonly application: IApplication, + @inject(ROUTE_REPOSITORY_SYMBOL) private readonly routeRepository: RouteRepository + ) {} + + public async getRoutes(routeQueryParams: RouteQueryParams): Promise> { + const { limit } = routeQueryParams; + + let elasticResponse: estypes.SearchResponse | undefined = undefined; + if (routeQueryParams.controlPoint ?? '') { + elasticResponse = await this.routeRepository.getControlPointInRoute( + routeQueryParams as RouteQueryParams & Required>, + limit + ); + } else { + elasticResponse = await this.routeRepository.getRoutes(routeQueryParams, limit); + } + + return formatResponse(elasticResponse, routeQueryParams, this.application.controlObjectDisplayNamePrefixes); + } +} diff --git a/src/route/routes/routeRouter.ts b/src/control/route/routes/routeRouter.ts similarity index 100% rename from src/route/routes/routeRouter.ts rename to src/control/route/routes/routeRouter.ts diff --git a/src/control/tile/DAL/queries.ts b/src/control/tile/DAL/queries.ts new file mode 100644 index 00000000..35dadf7a --- /dev/null +++ b/src/control/tile/DAL/queries.ts @@ -0,0 +1,113 @@ +import { estypes } from '@elastic/elasticsearch'; +import { BBox } from 'geojson'; +import { CommonRequestParameters } from '../../../common/interfaces'; +import { ELASTIC_KEYWORDS } from '../../constants'; +import { ConvertSnakeToCamelCase, geoContextQuery, parseGeo } from '../../../common/utils'; + +export interface TileQueryParams extends ConvertSnakeToCamelCase { + tile?: string; + mgrs?: string; + subTile?: string; +} + +export const queryForTiles = ({ + tile, + geoContext, + geoContextMode, + disableFuzziness, +}: Omit & Required>): estypes.SearchRequest => ({ + query: { + bool: { + must: [ + { + term: { + [ELASTIC_KEYWORDS.type]: 'TILE', + }, + }, + { + match: { + [ELASTIC_KEYWORDS.tileName]: { + query: tile, + fuzziness: disableFuzziness ? undefined : 1, + // eslint-disable-next-line @typescript-eslint/naming-convention + prefix_length: 1, + }, + }, + }, + ], + ...geoContextQuery(geoContext, geoContextMode), + }, + }, +}); + +export const queryForSubTiles = ({ + tile, + geoContext, + geoContextMode, + subTile, + disableFuzziness, +}: Omit, 'mgrs'>): estypes.SearchRequest => ({ + query: { + bool: { + must: [ + { + term: { + [ELASTIC_KEYWORDS.type]: 'SUB_TILE', + }, + }, + { + term: { + [ELASTIC_KEYWORDS.tileName]: tile, + }, + }, + { + match: { + [ELASTIC_KEYWORDS.subTileId]: { + query: subTile, + fuzziness: disableFuzziness ? undefined : 1, + // eslint-disable-next-line @typescript-eslint/naming-convention + prefix_length: 1, + }, + }, + }, + ], + ...geoContextQuery(geoContext, geoContextMode), + }, + }, +}); + +export const queryForTilesByBbox = ({ + bbox, + geoContext, + geoContextMode, +}: { bbox: BBox } & ConvertSnakeToCamelCase): estypes.SearchRequest => { + const { filter: geoCOntextQueryFilter, should } = geoContextQuery(geoContext, geoContextMode); + + const query: estypes.SearchRequest = { + query: { + bool: { + must: [ + { + term: { + ['properties.TYPE.keyword']: 'TILE', + }, + }, + ], + filter: [ + { + // eslint-disable-next-line @typescript-eslint/naming-convention + geo_shape: { + [ELASTIC_KEYWORDS.geometry]: { + shape: parseGeo({ bbox }), + relation: 'intersects', + }, + }, + }, + ...(geoCOntextQueryFilter ?? []), + ], + should, + }, + }, + }; + return query; +}; diff --git a/src/control/tile/DAL/tileRepository.ts b/src/control/tile/DAL/tileRepository.ts new file mode 100644 index 00000000..336818e4 --- /dev/null +++ b/src/control/tile/DAL/tileRepository.ts @@ -0,0 +1,49 @@ +import { estypes } from '@elastic/elasticsearch'; +import { FactoryFunction } from 'tsyringe'; +import { BBox } from 'geojson'; +import { ElasticClient, ElasticClients } from '../../../common/elastic'; +import { Tile } from '../models/tile'; +import { queryElastic } from '../../../common/elastic/utils'; +import { IConfig } from '../../../common/interfaces'; +import { SERVICES } from '../../../common/constants'; +import { additionalControlSearchProperties } from '../../utils'; +import { queryForTiles, queryForSubTiles, TileQueryParams, queryForTilesByBbox } from './queries'; + +// eslint-disable-next-line @typescript-eslint/explicit-function-return-type +const createTileRepository = (client: ElasticClient, config: IConfig) => { + return { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + async getTiles(tileQueryParams: TileQueryParams & Required>): Promise> { + const response = await queryElastic(client, { + ...additionalControlSearchProperties(config, tileQueryParams.limit), + ...queryForTiles(tileQueryParams), + }); + + return response; + }, + + async getSubTiles(tileQueryParams: Required): Promise> { + const response = await queryElastic(client, { + ...additionalControlSearchProperties(config, tileQueryParams.limit), + ...queryForSubTiles(tileQueryParams), + }); + + return response; + }, + async getTilesByBbox(searchParams: { bbox: BBox } & Omit): Promise> { + const response = await queryElastic(client, { + ...additionalControlSearchProperties(config, searchParams.limit), + ...queryForTilesByBbox(searchParams), + }); + return response; + }, + }; +}; + +export type TileRepository = ReturnType; + +export const tileRepositoryFactory: FactoryFunction = (depContainer) => { + return createTileRepository(depContainer.resolve(SERVICES.ELASTIC_CLIENTS).control, depContainer.resolve(SERVICES.CONFIG)); +}; + +export const TILE_REPOSITORY_SYMBOL = Symbol('TILE_REPOSITORY_SYMBOL'); diff --git a/src/tile/controllers/tileController.ts b/src/control/tile/controllers/tileController.ts similarity index 67% rename from src/tile/controllers/tileController.ts rename to src/control/tile/controllers/tileController.ts index 3e86c335..1bd158e1 100644 --- a/src/tile/controllers/tileController.ts +++ b/src/control/tile/controllers/tileController.ts @@ -4,10 +4,10 @@ import { BoundCounter, Meter } from '@opentelemetry/api-metrics'; import { RequestHandler } from 'express'; import httpStatus from 'http-status-codes'; import { injectable, inject } from 'tsyringe'; -import { SERVICES } from '../../common/constants'; +import { SERVICES } from '../../../common/constants'; import { TileManager } from '../models/tileManager'; import { Tile } from '../models/tile'; -import { FeatureCollection } from '../../common/interfaces'; +import { CommonRequestParameters, FeatureCollection } from '../../../common/interfaces'; type GetTilesHandler = RequestHandler< undefined, @@ -20,11 +20,10 @@ type GetTilesHandler = RequestHandler< GetTilesQueryParams >; -export interface GetTilesQueryParams { - tile: string; +export interface GetTilesQueryParams extends CommonRequestParameters { + tile?: string; + mgrs?: string; sub_tile?: string; - reduce_fuzzy_match?: string; - size?: string; } @injectable() @@ -41,12 +40,16 @@ export class TileController { public getTiles: GetTilesHandler = async (req, res, next) => { try { - const { tile, sub_tile, reduce_fuzzy_match, size } = req.query; - const response = await this.manager.getTiles( - { tile, subTile: sub_tile ? parseInt(sub_tile) : undefined }, - reduce_fuzzy_match == 'true', - size ? parseInt(size) : undefined - ); + const { tile, sub_tile, disable_fuzziness, geo_context, geo_context_mode, limit, mgrs } = req.query; + const response = await this.manager.getTiles({ + tile, + subTile: sub_tile, + disableFuzziness: disable_fuzziness, + geoContext: geo_context, + geoContextMode: geo_context_mode, + limit, + mgrs, + }); return res.status(httpStatus.OK).json(response); } catch (error: unknown) { this.logger.warn('tileController.getTiles Error:', error); diff --git a/src/control/tile/models/tile.ts b/src/control/tile/models/tile.ts new file mode 100644 index 00000000..5b58d885 --- /dev/null +++ b/src/control/tile/models/tile.ts @@ -0,0 +1,11 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Feature, GeoJsonProperties } from 'geojson'; + +export interface Tile extends Feature { + properties: GeoJsonProperties & { + TYPE: 'TILE' | 'SUB_TILE'; + TILE_NAME?: string; + SUB_TILE_ID?: string; + LAYER_NAME?: string; + }; +} diff --git a/src/control/tile/models/tileManager.ts b/src/control/tile/models/tileManager.ts new file mode 100644 index 00000000..0825d120 --- /dev/null +++ b/src/control/tile/models/tileManager.ts @@ -0,0 +1,55 @@ +import { IConfig } from 'config'; +import { Logger } from '@map-colonies/js-logger'; +import { inject, injectable } from 'tsyringe'; +import { estypes } from '@elastic/elasticsearch'; +import * as mgrs from 'mgrs'; +import { BBox } from 'geojson'; +import { SERVICES } from '../../../common/constants'; +import { TILE_REPOSITORY_SYMBOL, TileRepository } from '../DAL/tileRepository'; +import { formatResponse } from '../../utils'; +import { TileQueryParams } from '../DAL/queries'; +import { FeatureCollection, IApplication } from '../../../common/interfaces'; +import { BadRequestError } from '../../../common/errors'; +import { Tile } from './tile'; + +@injectable() +export class TileManager { + public constructor( + @inject(SERVICES.LOGGER) private readonly logger: Logger, + @inject(SERVICES.CONFIG) private readonly config: IConfig, + @inject(SERVICES.APPLICATION) private readonly application: IApplication, + @inject(TILE_REPOSITORY_SYMBOL) private readonly tileRepository: TileRepository + ) {} + + public async getTiles(tileQueryParams: TileQueryParams): Promise> { + if ( + (tileQueryParams.tile === undefined && tileQueryParams.mgrs === undefined) || + (tileQueryParams.tile !== undefined && tileQueryParams.mgrs !== undefined) + ) { + throw new BadRequestError("/control/tiles: only one of 'tile' or 'mgrs' query parameter must be defined"); + } + + let elasticResponse: estypes.SearchResponse | undefined = undefined; + + if (tileQueryParams.mgrs !== undefined) { + let bbox: BBox = [0, 0, 0, 0]; + try { + bbox = mgrs.inverse(tileQueryParams.mgrs); + bbox.forEach((coord) => { + if (isNaN(coord)) { + throw new Error('Invalid MGRS'); + } + }); + } catch (error) { + throw new BadRequestError(`Invalid MGRS: ${tileQueryParams.mgrs}`); + } + elasticResponse = await this.tileRepository.getTilesByBbox({ bbox, ...tileQueryParams }); + } else if (tileQueryParams.subTile ?? '') { + elasticResponse = await this.tileRepository.getSubTiles(tileQueryParams as Required); + } else { + elasticResponse = await this.tileRepository.getTiles(tileQueryParams as TileQueryParams & Required>); + } + + return formatResponse(elasticResponse, tileQueryParams, this.application.controlObjectDisplayNamePrefixes); + } +} diff --git a/src/tile/routes/tileRouter.ts b/src/control/tile/routes/tileRouter.ts similarity index 100% rename from src/tile/routes/tileRouter.ts rename to src/control/tile/routes/tileRouter.ts diff --git a/src/control/utils.ts b/src/control/utils.ts new file mode 100644 index 00000000..cabace51 --- /dev/null +++ b/src/control/utils.ts @@ -0,0 +1,103 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ +/* eslint-disable @typescript-eslint/naming-convention */ +import { estypes } from '@elastic/elasticsearch'; +import { CommonRequestParameters, GenericGeocodingResponse, IApplication, IConfig } from '../common/interfaces'; +import { elasticConfigPath } from '../common/constants'; +import { ElasticDbClientsConfig } from '../common/elastic/interfaces'; +import { Item } from '../control/item/models/item'; +import { Tile } from '../control/tile/models/tile'; +import { Route } from '../control/route/models/route'; +import { ConvertSnakeToCamelCase } from '../common/utils'; +import { CONTROL_FIELDS } from './constants'; + +const LAST_ELEMENT_INDEX = -1; + +const generateDisplayName = ( + source: T, + displayNamePrefixes: IApplication['controlObjectDisplayNamePrefixes'] +): (string | undefined)[] => { + const sourceType = + source.properties.TYPE === 'ITEM' && (source as Item).properties.TIED_TO !== undefined ? 'CONTROL_POINT' : source.properties.TYPE; + + const name: (string | undefined)[] = []; + sourceType === 'TILE' && name.unshift((source as Tile).properties.TILE_NAME); + sourceType === 'SUB_TILE' && name.unshift((source as Tile).properties.SUB_TILE_ID); + sourceType === 'ITEM' && name.unshift((source as Item).properties.OBJECT_COMMAND_NAME); + sourceType === 'ROUTE' && name.unshift((source as Route).properties.OBJECT_COMMAND_NAME); + sourceType === 'CONTROL_POINT' && name.unshift((source as Item).properties.OBJECT_COMMAND_NAME); + + name.unshift(displayNamePrefixes[sourceType]); + + if (sourceType === 'SUB_TILE') { + name.unshift((source as Tile).properties.TILE_NAME); + name.unshift(displayNamePrefixes['TILE']); + } + + if (sourceType === 'CONTROL_POINT') { + name.unshift((source as Item).properties.TIED_TO); + name.unshift(displayNamePrefixes['ROUTE']); + } + + return name; +}; + +export const convertCamelToSnakeCase = (obj: Record): Record => { + const snakeCaseObj: Record = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const snakeCaseKey = key.replace(/[A-Z]/g, (match) => `_${match.toLowerCase()}`); + snakeCaseObj[snakeCaseKey] = obj[key]; + } + } + return snakeCaseObj; +}; + +export const formatResponse = ( + elasticResponse: estypes.SearchResponse, + requestParams: CommonRequestParameters | ConvertSnakeToCamelCase, + displayNamePrefixes: IApplication['controlObjectDisplayNamePrefixes'] +): GenericGeocodingResponse => ({ + type: 'FeatureCollection', + geocoding: { + version: process.env.npm_package_version, + query: convertCamelToSnakeCase(requestParams as Record), + response: { + results_count: elasticResponse.hits.hits.length, + max_score: elasticResponse.hits.max_score ?? 0, + match_latency_ms: elasticResponse.took, + }, + }, + features: [ + ...(elasticResponse.hits.hits.map(({ _source: source, _score: score, _index: index }) => { + if (source !== undefined) { + const generatedDisplayName = generateDisplayName(source, displayNamePrefixes); + return { + ...source, + properties: { + ...source.properties, + matches: [ + { + layer: source.properties.LAYER_NAME, + source: index, + source_id: [], + }, + ], + names: { + default: [generatedDisplayName.at(LAST_ELEMENT_INDEX)], + display: generatedDisplayName.join(' '), + }, + score, + }, + }; + } + }) as GenericGeocodingResponse['features']), + ], +}); + +// eslint-disable-next-line @typescript-eslint/naming-convention +export const additionalControlSearchProperties = (config: IConfig, size: number): Pick => ({ + size, + index: config.get(elasticConfigPath).control.properties.index as string, + // eslint-disable-next-line @typescript-eslint/naming-convention + _source: CONTROL_FIELDS, +}); diff --git a/src/geotextSearch/interfaces.ts b/src/geotextSearch/interfaces.ts deleted file mode 100644 index b57fc493..00000000 --- a/src/geotextSearch/interfaces.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { GeoJSON } from 'geojson'; -import { HierarchySearchHit } from './models/elasticsearchHits'; - -export interface PlaceType { - placetype: string; - confidence: number; -} - -export interface TokenResponse { - tokens: string[]; - prediction: string[]; -} - -export interface TextSearchParams { - query: string; - viewbox?: GeoJSON; - boundary?: GeoJSON; - sources?: string[]; - regions?: string[]; - name?: string; - placeTypes?: string[]; - subPlaceTypes?: string[]; - hierarchies: HierarchySearchHit[]; - limit: number; -} - -export interface GetGeotextSearchParams { - query: string; - limit: number; - source?: string[]; - viewbox?: string; - boundary?: string; - region?: string[]; -} - -/* eslint-disable @typescript-eslint/naming-convention */ - -export interface QueryResult { - type: string; - geocoding: { version?: string; query: TextSearchParams; name?: string }; - features: { - type: string; - geometry?: GeoJSON; - properties: { - rank: number; - source?: string; - source_id?: string[]; - layer?: string; - name: { - [key: string]: string | string[] | undefined; - }; - highlight?: Record; - placetype?: string; - sub_placetype?: string; - region?: string[]; - sub_region?: string[]; - regions?: { region: string; sub_regions: string[] }[]; - }; - }[]; -} -/* eslint-enable @typescript-eslint/naming-convention */ - -//Defenitions -export const POINT_LENGTH = 2; -export const BBOX_LENGTH = 4; -export const INDEX_NOT_FOUND = -1; diff --git a/src/geotextSearch/utils.ts b/src/geotextSearch/utils.ts deleted file mode 100644 index 7324f961..00000000 --- a/src/geotextSearch/utils.ts +++ /dev/null @@ -1,139 +0,0 @@ -// import fetch, { Response } from "node-fetch-commonjs"; -import { GeoJSON } from 'geojson'; -import { SearchHit } from '@elastic/elasticsearch/lib/api/types'; -import { StatusCodes } from 'http-status-codes'; -import axios, { AxiosResponse as Response } from 'axios'; -import { InternalServerError } from '../common/errors'; -import { IApplication } from '../common/interfaces'; -import { BBOX_LENGTH, POINT_LENGTH, QueryResult, TextSearchParams } from './interfaces'; -import { generateDisplayName } from './parsing'; -import { TextSearchHit } from './models/elasticsearchHits'; - -const FIND_QUOTES = /["']/g; - -const FIND_SPECIAL = /[`!@#$%^&*()_\-+=|\\/,.<>:[\]{}\n\t\r\s;؛]+/g; - -export const fetchNLPService = async (endpoint: string, requestData: object): Promise => { - let res: Response | null = null, - data: T[] | undefined | null = null; - try { - res = await axios.post(endpoint, requestData); - } catch (err: unknown) { - if (axios.isAxiosError(err)) { - throw new InternalServerError(`NLP analyser is not available - ${err.message}`); - } - throw new InternalServerError('fetchNLPService: Unknown error' + JSON.stringify(err)); - } - - try { - // data = (await res.json()) as T[] | undefined; - data = res?.data as T[] | undefined; - } catch (_) { - throw new InternalServerError("Couldn't convert the response from NLP service to JSON"); - } - - if (res?.status !== StatusCodes.OK || !data || data.length < 1 || !data[0]) { - throw new InternalServerError(JSON.stringify(data)); - } - return data; -}; - -export const cleanQuery = (query: string): string[] => query.replace(FIND_QUOTES, '').split(FIND_SPECIAL); - -export const parsePoint = (split: string[]): GeoJSON => ({ - type: 'Point', - coordinates: split.map(Number), -}); - -export const parseBbox = (split: string[]): GeoJSON => { - const [xMin, yMin, xMax, yMax] = split.map(Number); - return { - type: 'Polygon', - coordinates: [ - [ - [xMin, yMin], - [xMin, yMax], - [xMax, yMax], - [xMax, yMin], - [xMin, yMin], - ], - ], - }; -}; - -export const parseGeo = (input: string | GeoJSON): GeoJSON | undefined => { - if (typeof input === 'string') { - const splitted = input.split(','); - const converted = splitted.map(Number); - - if (converted.findIndex((x) => isNaN(x)) < 0) { - switch (splitted.length) { - case POINT_LENGTH: - // Point - return parsePoint(splitted); - case BBOX_LENGTH: - //BBOX - return parseBbox(splitted); - default: - return undefined; - } - } - } - - return input as GeoJSON; -}; - -/* eslint-disable @typescript-eslint/naming-convention */ -export const convertResult = ( - params: TextSearchParams, - results: SearchHit[], - { - sources, - regionCollection, - nameKeys, - mainLanguageRegex, - }: { - sources?: IApplication['sources']; - regionCollection?: IApplication['regions']; - nameKeys: IApplication['nameTranslationsKeys']; - mainLanguageRegex: IApplication['mainLanguageRegex']; - } = { nameKeys: [], mainLanguageRegex: '' } -): QueryResult => ({ - type: 'FeatureCollection', - geocoding: { - version: process.env.npm_package_version, - query: { - ...params, - }, - name: params.name, - }, - features: results.map(({ highlight, _source: feature }, index): QueryResult['features'][number] => { - const allNames = [feature!.text, feature!.translated_text || []]; - return { - type: 'Feature', - geometry: feature?.geo_json, - properties: { - rank: index + 1, - source: (sources ?? {})[feature?.source ?? ''] ?? feature?.source, - layer: feature?.layer_name, - source_id: feature?.source_id.map((id) => id.replace(/(^\{)|(\}$)/g, '')), // TODO: check if to remove this - name: { - [nameKeys[0]]: new RegExp(mainLanguageRegex).test(feature!.text[0]) ? allNames.shift() : allNames.pop(), - [nameKeys[1]]: allNames.pop(), - ['default']: [feature!.name], - display: highlight ? generateDisplayName(highlight.text, params.query!.split(' ').length, params.name) : feature!.name, - }, - highlight, - placetype: feature?.placetype, // TODO: check if to remove this - sub_placetype: feature?.sub_placetype, - regions: feature?.region.map((region) => ({ - region: region, - sub_regions: feature.sub_region.filter((sub_region) => (regionCollection ?? {})[region ?? '']?.includes(sub_region)), - })), - region: feature?.region, - sub_region: feature?.sub_region, - }, - }; - }), -}); -/* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/index.ts b/src/index.ts index 4ee6fbac..a2f0f74b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -21,8 +21,8 @@ void getApp() const logger = container.resolve(SERVICES.LOGGER); const server = createTerminus(createServer(app), { // eslint-disable-next-line @typescript-eslint/naming-convention - // healthChecks: { '/liveness': container.resolve(HEALTHCHECK) }, - // onSignal: container.resolve(ON_SIGNAL), + healthChecks: { '/liveness': container.resolve(HEALTHCHECK) }, + onSignal: container.resolve(ON_SIGNAL), }); server.listen(port, () => { diff --git a/src/item/DAL/queries.ts b/src/item/DAL/queries.ts deleted file mode 100644 index ec81de64..00000000 --- a/src/item/DAL/queries.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { estypes } from '@elastic/elasticsearch'; -import { boundingBox, geoDistance } from '../../common/elastic/utils'; -import { GeoContext } from '../../common/interfaces'; - -export interface ItemQueryParams { - commandName: string; - tile?: string; - subTile?: number; - geo?: GeoContext; -} - -/* eslint-disable @typescript-eslint/naming-convention */ -export const queryForItems = (params: ItemQueryParams): estypes.SearchRequest => ({ - query: { - bool: { - should: [ - { - term: { - 'properties.TYPE.keyword': 'ITEM', - }, - }, - ], - must: [ - { - match: { - 'properties.OBJECT_COMMAND_NAME.keyword': { - query: params.commandName, - fuzziness: 1, - prefix_length: 1, - }, - }, - }, - ...(params.tile ?? '' - ? [ - { - term: { - 'properties.TILE_NAME.keyword': params.tile, - }, - }, - ] - : []), - ...(params.subTile ?? 0 - ? [ - { - term: { - 'properties.SUB_TILE_ID.keyword': params.subTile, - }, - }, - ] - : []), - ...(params.geo?.bbox ? [boundingBox(params.geo.bbox)] : []), - ...((params.geo?.radius ?? 0) && (params.geo?.lat ?? 0) && (params.geo?.lon ?? 0) - ? [ - geoDistance( - params.geo as { - radius: number; - lon: number; - lat: number; - } - ), - ] - : []), - ], - }, - }, -}); diff --git a/src/item/models/item.ts b/src/item/models/item.ts deleted file mode 100644 index 3869c449..00000000 --- a/src/item/models/item.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Feature } from 'geojson'; - -export interface Item extends Feature {} diff --git a/src/item/models/itemManager.ts b/src/item/models/itemManager.ts deleted file mode 100644 index 8c17dbac..00000000 --- a/src/item/models/itemManager.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { IConfig } from 'config'; -import { Logger } from '@map-colonies/js-logger'; -import { inject, injectable } from 'tsyringe'; -import { estypes } from '@elastic/elasticsearch'; -import { SERVICES } from '../../common/constants'; -import { ITEM_REPOSITORY_SYMBOL, ItemRepository } from '../DAL/itemRepository'; -import { ItemQueryParams } from '../DAL/queries'; -import { formatResponse } from '../../common/utils'; -import { FeatureCollection } from '../../common/interfaces'; -import { getElasticClientQuerySize } from '../../common/elastic/utils'; -import { Item } from './item'; - -@injectable() -export class ItemManager { - public constructor( - @inject(SERVICES.LOGGER) private readonly logger: Logger, - @inject(SERVICES.CONFIG) private readonly config: IConfig, - @inject(ITEM_REPOSITORY_SYMBOL) private readonly itemRepository: ItemRepository - ) {} - - public async getItems(itemQueryParams: ItemQueryParams, reduceFuzzyMatch = false, size?: number): Promise> { - let elasticResponse: estypes.SearchResponse | undefined = undefined; - elasticResponse = await this.itemRepository.getItems(itemQueryParams, size ?? getElasticClientQuerySize(this.config, 'control')); - - const formattedResponse = formatResponse(elasticResponse); - - if (reduceFuzzyMatch && formattedResponse.features.length > 0) { - const filterFunction = (hit: Item | undefined): hit is Item => hit?.properties?.OBJECT_COMMAND_NAME === itemQueryParams.commandName; - formattedResponse.features = formattedResponse.features.filter(filterFunction); - } - - return formattedResponse; - } -} diff --git a/src/latLon/DAL/latLon.ts b/src/latLon/DAL/latLon.ts deleted file mode 100644 index 7f057e7b..00000000 --- a/src/latLon/DAL/latLon.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Column, Entity, PrimaryColumn } from 'typeorm'; -import { LatLon as ILatLon } from '../models/latLon'; - -@Entity({ name: 'tile_lat_lon' }) -export class LatLon implements ILatLon { - @PrimaryColumn({ name: 'pk' }) - public primaryKey!: number; - - @Column({ name: 'tile_name' }) - public tileName!: string; - - @Column({ name: 'zone' }) - public zone!: string; - - @Column({ name: 'min_x' }) - public minX!: string; - - @Column({ name: 'min_y' }) - public minY!: string; - - @Column({ name: 'ext_min_x' }) - public extMinX!: number; - - @Column({ name: 'ext_min_y' }) - public extMinY!: number; - - @Column({ name: 'ext_max_x' }) - public extMaxX!: number; - - @Column({ name: 'ext_max_y' }) - public extMaxY!: number; -} diff --git a/src/latLon/DAL/latLonDAL.ts b/src/latLon/DAL/latLonDAL.ts index 7faf6c6b..fff62f1b 100644 --- a/src/latLon/DAL/latLonDAL.ts +++ b/src/latLon/DAL/latLonDAL.ts @@ -1,15 +1,22 @@ +import fs from 'fs'; import { Logger } from '@map-colonies/js-logger'; import cron from 'node-cron'; import { FactoryFunction, inject, injectable } from 'tsyringe'; import { InternalServerError } from '../../common/errors'; import { IApplication } from '../../common/interfaces'; import { SERVICES } from '../../common/constants'; -import { LATLON_CUSTOM_REPOSITORY_SYMBOL, LatLonRepository } from './latLonRepository'; -import { LatLon as LatLonDb } from './latLon'; +import { LatLon as ILatLon } from '../models/latLon'; +import { ConvertCamelToSnakeCase } from '../../common/utils'; +import { S3_REPOSITORY_SYMBOL, S3Repository } from '../../common/s3/s3Repository'; + +type LatLon = ConvertCamelToSnakeCase; +let scheduledTask: cron.ScheduledTask | null = null; + +let latLonDALInstance: LatLonDAL | null = null; @injectable() export class LatLonDAL { - private readonly latLonMap: Map; + private readonly latLonMap: Map; private onGoingUpdate: boolean; private dataLoad: | { @@ -22,12 +29,13 @@ export class LatLonDAL { public constructor( @inject(SERVICES.LOGGER) private readonly logger: Logger, - @inject(LATLON_CUSTOM_REPOSITORY_SYMBOL) private readonly latLonRepository: LatLonRepository + @inject(S3_REPOSITORY_SYMBOL) private readonly latLonRepository: S3Repository ) { - this.latLonMap = new Map(); + this.latLonMap = new Map(); this.onGoingUpdate = true; this.dataLoad = undefined; this.dataLoadError = false; + this.init().catch((error) => { this.logger.error('Failed to initialize lat-lon data', error); this.dataLoadError = true; @@ -38,6 +46,10 @@ export class LatLonDAL { public getOnGoingUpdate(): boolean { return this.onGoingUpdate; } + + public getIsDataLoadError(): boolean { + return this.dataLoadError; + } /* istanbul ignore end */ public async init(): Promise { @@ -62,7 +74,7 @@ export class LatLonDAL { this.logger.debug('latLonData initialized'); } catch (error) { - this.logger.error('Failed to initialize latLon data', error); + this.logger.error(`Failed to initialize latLon data. Error: ${(error as Error).message}`); this.dataLoadError = true; } finally { this.onGoingUpdate = false; @@ -70,22 +82,14 @@ export class LatLonDAL { } } - public async latLonToTile({ x, y, zone }: { x: number; y: number; zone: number }): Promise { - if (this.dataLoadError) { + public async latLonToTile({ x, y, zone }: { x: number; y: number; zone: number }): Promise { + if (this.getIsDataLoadError()) { throw new InternalServerError('Lat-lon to tile data currently not available'); } await this.dataLoad?.promise; return this.latLonMap.get(`${x},${y},${zone}`); } - public async tileToLatLon(tileName: string): Promise { - if (this.dataLoadError) { - throw new InternalServerError('Tile to lat-lon data currently not available'); - } - await this.dataLoad?.promise; - return this.latLonMap.get(tileName); - } - private clearLatLonMap(): void { this.logger.debug('Clearing latLon data'); this.latLonMap.clear(); @@ -96,19 +100,40 @@ export class LatLonDAL { this.clearLatLonMap(); - const latLonData = await this.latLonRepository.getAll(); + const latLonDataPath = await this.latLonRepository.downloadFile('latLonConvertionTable'); + + const { items: latLonData } = JSON.parse(await fs.promises.readFile(latLonDataPath, 'utf8')) as { items: LatLon[] }; + latLonData.forEach((latLon) => { - this.latLonMap.set(latLon.tileName, latLon); - this.latLonMap.set(`${latLon.minX},${latLon.minY},${latLon.zone}`, latLon); + this.latLonMap.set(`${latLon.min_x},${latLon.min_y},${latLon.zone}`, latLon); }); - this.logger.debug('latLon data loaded'); + + try { + await fs.promises.unlink(latLonDataPath); + } catch (error) { + this.logger.error(`Failed to delete latLonData file ${latLonDataPath}. Error: ${(error as Error).message}`); + } + this.logger.info('loadLatLonData: update completed'); } } export const cronLoadTileLatLonDataSymbol = Symbol('cronLoadTileLatLonDataSymbol'); -export const cronLoadTileLatLonDataFactory: FactoryFunction = (dependencyContainer) => { - const latLonDAL = dependencyContainer.resolve(LatLonDAL); +export const latLonDalSymbol = Symbol('latLonDalSymbol'); +export const latLonSignletonFactory: FactoryFunction = (dependencyContainer) => { + const logger = dependencyContainer.resolve(SERVICES.LOGGER); + const s3Repository = dependencyContainer.resolve(S3_REPOSITORY_SYMBOL); + + if (latLonDALInstance !== null) { + return latLonDALInstance; + } + + latLonDALInstance = new LatLonDAL(logger, s3Repository); + return latLonDALInstance; +}; + +export const cronLoadTileLatLonDataFactory: FactoryFunction = (dependencyContainer) => { + const latLonDAL = dependencyContainer.resolve(latLonDalSymbol); const logger = dependencyContainer.resolve(SERVICES.LOGGER); const cronPattern: string | undefined = dependencyContainer.resolve(SERVICES.APPLICATION).cronLoadTileLatLonDataPattern; @@ -118,18 +143,16 @@ export const cronLoadTileLatLonDataFactory: FactoryFunction = (dependencyC } /* istanbul ignore next */ - cron.schedule(cronPattern, () => { + scheduledTask = cron.schedule(cronPattern, () => { if (!latLonDAL.getOnGoingUpdate()) { logger.info('cronLoadTileLatLonData: starting update'); - latLonDAL - .init() - .then(() => logger.info('cronLoadTileLatLonData: update completed')) - .catch((error) => { - logger.error('cronLoadTileLatLonData: update failed', error); - }); + latLonDAL.init().catch((error) => { + logger.error('cronLoadTileLatLonData: update failed', error); + }); } else { logger.info('cronLoadTileLatLonData: update is already in progress'); } }); + return scheduledTask; /* istanbul ignore end */ }; diff --git a/src/latLon/DAL/latLonRepository.ts b/src/latLon/DAL/latLonRepository.ts deleted file mode 100644 index 6dc7dcab..00000000 --- a/src/latLon/DAL/latLonRepository.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { Logger } from '@map-colonies/js-logger'; -import { DataSource } from 'typeorm'; -import { FactoryFunction } from 'tsyringe'; -import { SERVICES } from '../../common/constants'; -import { LatLon as LatLonDb } from './latLon'; - -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const createLatLonRepository = (dataSource: DataSource, logger: Logger) => { - return dataSource.getRepository(LatLonDb).extend({ - async getAll(): Promise { - const result = await this.find(); - return result; - }, - }); -}; - -export type LatLonRepository = ReturnType; - -export const latLonRepositoryFactory: FactoryFunction = (depContainer) => { - return createLatLonRepository(depContainer.resolve(DataSource), depContainer.resolve(SERVICES.LOGGER)); -}; - -export const LATLON_CUSTOM_REPOSITORY_SYMBOL = Symbol('LATLON_CUSTOM_REPOSITORY_SYMBOL'); diff --git a/src/latLon/controllers/latLonController.ts b/src/latLon/controllers/latLonController.ts index 00633d6a..c16b601f 100644 --- a/src/latLon/controllers/latLonController.ts +++ b/src/latLon/controllers/latLonController.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import { Feature } from 'geojson'; import { Logger } from '@map-colonies/js-logger'; import { BoundCounter, Meter } from '@opentelemetry/api-metrics'; import { RequestHandler } from 'express'; @@ -6,38 +7,12 @@ import httpStatus from 'http-status-codes'; import { injectable, inject } from 'tsyringe'; import { SERVICES } from '../../common/constants'; import { LatLonManager } from '../models/latLonManager'; -import { Tile } from '../../tile/models/tile'; -import { FeatureCollection, WGS84Coordinate } from '../../common/interfaces'; +import { WGS84Coordinate } from '../../common/interfaces'; +/* istanbul ignore file */ -type GetLatLonToTileHandler = RequestHandler< - undefined, - { - tileName: string; - subTileNumber: number[]; - }, - undefined, - GetLatLonToTileQueryParams ->; +type GetCoordinatesHandler = RequestHandler; -type GetTileToLatLonHandler = RequestHandler, undefined, GetTileToLatLonQueryParams>; - -type GetLatLonToMgrsHandler = RequestHandler; - -type GetMgrsToLatLonHandler = RequestHandler; - -export interface GetLatLonToTileQueryParams extends WGS84Coordinate {} - -export interface GetTileToLatLonQueryParams { - tile: string; - sub_tile_number: number[]; -} - -export interface GetLatLonToMgrsQueryParams extends WGS84Coordinate { - accuracy?: number; -} -export interface GetMgrsToLatLonQueryParams { - mgrs: string; -} +export type GetCoordinatesRequestParams = WGS84Coordinate & { target_grid: 'control' | 'MGRS' }; @injectable() export class LatLonController { @@ -51,55 +26,25 @@ export class LatLonController { this.createdResourceCounter = meter.createCounter('created_resource'); } - public latlonToTile: GetLatLonToTileHandler = async (req, res, next) => { - try { - const { lat, lon } = req.query; - - const response = await this.manager.latLonToTile({ lat, lon }); - return res.status(httpStatus.OK).json(response); - } catch (error: unknown) { - this.logger.warn('latLonController.latlonToTile Error:', error); - next(error); - } - }; - - public tileToLatLon: GetTileToLatLonHandler = async (req, res, next) => { + public getCoordinates: GetCoordinatesHandler = async (req, res, next) => { try { - const { tile: tileName, sub_tile_number } = req.query; + const { target_grid: targetGrid } = req.query; - const response = await this.manager.tileToLatLon({ - tileName, - subTileNumber: sub_tile_number, - }); - return res.status(httpStatus.OK).json(response); - } catch (error: unknown) { - this.logger.warn('latLonController.tileToLatLon Error:', error); - next(error); - } - }; - - public latlonToMgrs: GetLatLonToMgrsHandler = (req, res, next) => { - try { - const { lat, lon, accuracy } = req.query; - - const response = this.manager.latLonToMGRS({ lat, lon, accuracy }); - - return res.status(httpStatus.OK).json(response); - } catch (error: unknown) { - this.logger.warn('latLonController.latlonToMgrs Error:', error); - next(error); - } - }; - - public mgrsToLatlon: GetMgrsToLatLonHandler = (req, res, next) => { - try { - const { mgrs } = req.query; + let response: + | ({ + [key: string]: unknown; + } & Feature) + | undefined = undefined; - const response = this.manager.mgrsToLatLon(mgrs); + if (targetGrid === 'control') { + response = await this.manager.latLonToTile({ ...req.query, targetGrid }); + } else { + response = this.manager.latLonToMGRS({ ...req.query, targetGrid }); + } return res.status(httpStatus.OK).json(response); } catch (error: unknown) { - this.logger.warn('latLonController.mgrsToLatlon Error:', error); + this.logger.warn('latLonController.getCoordinates Error:', error); next(error); } }; diff --git a/src/latLon/models/latLon.ts b/src/latLon/models/latLon.ts index ab1176ba..3fc70537 100644 --- a/src/latLon/models/latLon.ts +++ b/src/latLon/models/latLon.ts @@ -1,5 +1,4 @@ export interface LatLon { - primaryKey: number; tileName: string; zone: string; minX: string; diff --git a/src/latLon/models/latLonManager.ts b/src/latLon/models/latLonManager.ts index ee6732a0..c8db9eff 100644 --- a/src/latLon/models/latLonManager.ts +++ b/src/latLon/models/latLonManager.ts @@ -1,37 +1,30 @@ import { IConfig } from 'config'; import { Logger } from '@map-colonies/js-logger'; import { inject, injectable } from 'tsyringe'; +import { BBox, Feature } from 'geojson'; import * as mgrs from 'mgrs'; import { SERVICES } from '../../common/constants'; -import { LatLonDAL } from '../DAL/latLonDAL'; -import { convertWgs84ToUTM, validateTile, validateWGS84Coordinate } from '../../common/utils'; -import { convertTilesToUTM, getSubTileByBottomLeftUtmCoor, validateResult } from '../utlis'; +import { LatLonDAL, latLonDalSymbol } from '../DAL/latLonDAL'; +import { convertUTMToWgs84, convertWgs84ToUTM, parseGeo, validateWGS84Coordinate } from '../../common/utils'; import { BadRequestError } from '../../common/errors'; -import { Tile } from '../../tile/models/tile'; -import { FeatureCollection, WGS84Coordinate } from '../../common/interfaces'; +import { WGS84Coordinate } from '../../common/interfaces'; +import { convertCamelToSnakeCase } from '../../control/utils'; @injectable() export class LatLonManager { - private readonly dbSchema: string; - public constructor( @inject(SERVICES.LOGGER) private readonly logger: Logger, - @inject(LatLonDAL) private readonly latLonDAL: LatLonDAL, + @inject(latLonDalSymbol) private readonly latLonDAL: LatLonDAL, @inject(SERVICES.CONFIG) private readonly config: IConfig - ) { - this.dbSchema = this.config.get('db.postgresql.schema'); - } + ) {} - public async latLonToTile({ lat, lon }: WGS84Coordinate): Promise<{ - tileName: string; - subTileNumber: number[]; - }> { + public async latLonToTile({ lat, lon, targetGrid }: WGS84Coordinate & { targetGrid: string }): Promise<{ [key: string]: unknown } & Feature> { if (!validateWGS84Coordinate({ lat, lon })) { this.logger.warn("LatLonManager.latLonToTile: Invalid lat lon, check 'lat' and 'lon' keys exists and their values are legal"); throw new BadRequestError("Invalid lat lon, check 'lat' and 'lon' keys exists and their values are legal"); } - const utm = convertWgs84ToUTM(lat, lon); + const utm = convertWgs84ToUTM({ longitude: lon, latitude: lat }); if (typeof utm === 'string') { this.logger.warn('LatLonManager.latLonToTile: utm is string'); @@ -51,51 +44,99 @@ export class LatLonManager { throw new BadRequestError('The coordinate is outside the grid extent'); } - const xNumber = Math.abs(Math.trunc((coordinatesUTM.x % 10000) / 10) * 10) + const xNumber = Math.abs(Math.trunc((utm.Easting % 10000) / 10) * 10) .toString() .padStart(4, '0'); - const yNumber = Math.abs(Math.trunc((coordinatesUTM.y % 10000) / 10) * 10) + const yNumber = Math.abs(Math.trunc((utm.Northing % 10000) / 10) * 10) .toString() .padStart(4, '0'); + const bbox = [ + ...( + Object.values(convertUTMToWgs84(tileCoordinateData.ext_min_x, tileCoordinateData.ext_min_y, +tileCoordinateData.zone)) as number[] + ).reverse(), + ...( + Object.values(convertUTMToWgs84(tileCoordinateData.ext_max_x, tileCoordinateData.ext_max_y, +tileCoordinateData.zone)) as number[] + ).reverse(), + ] as BBox; + return { - tileName: tileCoordinateData.tileName, - subTileNumber: new Array(3).fill('').map(function (value, i) { - return +(xNumber[i] + yNumber[i]); - }), + type: 'Feature', + geocoding: { + version: process.env.npm_package_version, + query: { + lat, + lon, + ...convertCamelToSnakeCase({ targetGrid }), + }, + response: convertCamelToSnakeCase({ + maxScore: 1, + resultsCount: 1, + matchLatencyMs: 0, + }), + }, + bbox, + geometry: parseGeo({ + bbox, + }) ?? { + type: 'Point', + coordinates: [lon, lat], + }, + properties: { + name: tileCoordinateData.tile_name, + tileName: tileCoordinateData.tile_name, + subTileNumber: new Array(3).fill('').map(function (value, i) { + return xNumber[i] + yNumber[i]; + }), + }, }; } - public async tileToLatLon({ tileName, subTileNumber }: { tileName: string; subTileNumber: number[] }): Promise> { - if (!validateTile({ tileName, subTileNumber })) { - const message = "Invalid tile, check that 'tileName' and 'subTileNumber' exists and subTileNumber is array of size 3 with positive integers"; - this.logger.warn(`LatLonManager.tileToLatLon: ${message}`); - throw new BadRequestError(message); - } - - const tile = await this.latLonDAL.tileToLatLon(tileName); - - if (!tile) { - const meessage = 'Tile not found'; - this.logger.warn(`LatLonManager.tileToLatLon: ${meessage}`); - throw new BadRequestError(meessage); - } - - const utmCoor = convertTilesToUTM({ tileName, subTileNumber }, tile); - validateResult(tile, utmCoor); - - const geojsonRes = getSubTileByBottomLeftUtmCoor(utmCoor, { tileName, subTileNumber }); - return geojsonRes; - } - - public latLonToMGRS({ lat, lon, accuracy = 5 }: { lat: number; lon: number; accuracy?: number }): { mgrs: string } { + public latLonToMGRS({ + lat, + lon, + accuracy = 5, + targetGrid, + }: { + lat: number; + lon: number; + accuracy?: number; + targetGrid: string; + }): { [key: string]: unknown } & Feature { + const accuracyString: Record = { + [0]: '100km', + [1]: '10km', + [2]: '1km', + [3]: '100m', + [4]: '10m', + [5]: '1m', + }; + const mgrsStr = mgrs.forward([lon, lat], accuracy); return { - mgrs: mgrs.forward([lon, lat], accuracy), + type: 'Feature', + geocoding: { + version: process.env.npm_package_version, + query: { + lat, + lon, + ...convertCamelToSnakeCase({ targetGrid }), + }, + response: convertCamelToSnakeCase({ + maxScore: 1, + resultsCount: 1, + matchLatencyMs: 0, + }), + }, + bbox: mgrs.inverse(mgrsStr), + geometry: { + type: 'Point', + coordinates: [lon, lat], + }, + properties: { + name: mgrsStr, + accuracy: accuracyString[accuracy], + mgrs: mgrsStr, + }, }; } - - public mgrsToLatLon(mgrsStr: string): { lat: number; lon: number } { - const [lon, lat] = mgrs.toPoint(mgrsStr); - return { lat, lon }; - } } diff --git a/src/latLon/routes/latLonRouter.ts b/src/latLon/routes/latLonRouter.ts index 534f765e..517178d0 100644 --- a/src/latLon/routes/latLonRouter.ts +++ b/src/latLon/routes/latLonRouter.ts @@ -6,10 +6,7 @@ const latLonRouterFactory: FactoryFunction = (dependencyContainer) => { const router = Router(); const controller = dependencyContainer.resolve(LatLonController); - router.get('/latlonToTile', controller.latlonToTile); - router.get('/tileToLatLon', controller.tileToLatLon); - router.get('/latlonToMgrs', controller.latlonToMgrs); - router.get('/mgrsToLatlon', controller.mgrsToLatlon); + router.get('/coordinates', controller.getCoordinates); return router; }; diff --git a/src/latLon/utlis/index.ts b/src/latLon/utlis/index.ts deleted file mode 100644 index aedc39ef..00000000 --- a/src/latLon/utlis/index.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Polygon } from 'geojson'; -import { BadRequestError } from '../../common/errors'; -import { convertUTMToWgs84 } from '../../common/utils'; -import { LatLon } from '../DAL/latLon'; -import { FeatureCollection } from '../../common/interfaces'; -import { Tile } from '../../tile/models/tile'; - -/* eslint-disable @typescript-eslint/naming-convention */ -const geoJsonObjectTemplate = (): FeatureCollection => ({ - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - type: 'Polygon', - coordinates: [[]], - }, - properties: { - TYPE: 'TILE', - }, - }, - ], -}); -/* eslint-enable @typescript-eslint/naming-convention */ - -export const convertTilesToUTM = ( - tile: { tileName: string; subTileNumber: number[] }, - tileObject: LatLon -): { - x: number; - y: number; - zone: number; -} => { - const xCoordinate = parseInt(tileObject.minX); - const yCoordinate = parseInt(tileObject.minY); - - const xCoordinatePart = tile.subTileNumber - .map((x) => { - return x.toString()[0]; - }) - .join(''); - const yCoordinatePart = tile.subTileNumber - .map(function (y) { - return y.toString()[1]; - }) - .join(''); - - return { x: xCoordinate + parseInt(xCoordinatePart) * 10, y: yCoordinate + parseInt(yCoordinatePart) * 10, zone: parseInt(tileObject.zone) }; -}; - -export const validateResult = ( - tile: LatLon, - utmCoor: { - x: number; - y: number; - zone: number; - } -): void => { - if (tile.extMinX > utmCoor.x || tile.extMaxX < utmCoor.x || tile.extMinY > utmCoor.y || tile.extMaxY < utmCoor.y) { - throw new BadRequestError("Tile is found, sub tile is not in tile's extent"); - } -}; - -export const getSubTileByBottomLeftUtmCoor = ( - utmCoor: { - x: number; - y: number; - zone: number; - }, - tile: { tileName: string; subTileNumber: number[] } -): FeatureCollection => { - const result = geoJsonObjectTemplate(); - - const multiplyByOrder = [ - [0, 0], - [1, 0], - [1, 1], - [0, 1], - [0, 0], - ]; // bottom left -> bottom right -> top right -> top left -> bottom left - const distance = 10; // 10 meters - const polygon: Polygon = { - type: 'Polygon', - coordinates: [[]], - }; - - for (const multiply of multiplyByOrder) { - const coordiante = convertUTMToWgs84(utmCoor.x + distance * multiply[0], utmCoor.y + distance * multiply[1], utmCoor.zone); - if (typeof coordiante === 'string') { - throw new Error('coordinate is string'); - } - polygon.coordinates[0].push([coordiante.lon, coordiante.lat]); - } - - result.features[0].geometry = polygon; - // eslint-disable-next-line @typescript-eslint/naming-convention - result.features[0].properties = { ...result.features[0].properties, TILE_NAME: tile.tileName, SUB_TILE_NUMBER: tile.subTileNumber }; - - return result; -}; diff --git a/src/geotextSearch/DAL/geotextSearchRepository.ts b/src/location/DAL/locationRepository.ts similarity index 89% rename from src/geotextSearch/DAL/geotextSearchRepository.ts rename to src/location/DAL/locationRepository.ts index 540b0f1a..72354959 100644 --- a/src/geotextSearch/DAL/geotextSearchRepository.ts +++ b/src/location/DAL/locationRepository.ts @@ -23,7 +23,7 @@ const createGeotextRepository = (client: ElasticClient, logger: Logger) => { const { tokens, prediction } = response[0]; - if (!tokens || !prediction) { + if (!tokens || !tokens.length || !prediction || !prediction.length) { logger.error('No tokens or prediction'); throw new BadRequestError('No tokens or prediction'); } @@ -33,10 +33,10 @@ const createGeotextRepository = (client: ElasticClient, logger: Logger) => { return nameTokens.join(' '); }, - async generatePlacetype(index: string, query: string): Promise<{ placeTypes: string[]; subPlaceTypes: string[] }> { + async generatePlacetype(index: string, query: string, disableFuzziness: boolean): Promise<{ placeTypes: string[]; subPlaceTypes: string[] }> { const { hits: { hits }, - } = await queryElastic(client, { index, ...placetypeQuery(query) }); + } = await queryElastic(client, { index, ...placetypeQuery(query, disableFuzziness) }); if (hits.length == 2 && hits[0]._score! - hits[1]._score! > 0.5) { hits.pop(); @@ -50,10 +50,10 @@ const createGeotextRepository = (client: ElasticClient, logger: Logger) => { return { placeTypes, subPlaceTypes }; }, - async extractHierarchy(index: string, query: string, hierarchyBoost: number): Promise { + async extractHierarchy(index: string, query: string, hierarchyBoost: number, disableFuzziness: boolean): Promise { const { hits: { hits }, - } = await queryElastic(client, { index, ...hierarchyQuery(query) }); + } = await queryElastic(client, { index, ...hierarchyQuery(query, disableFuzziness) }); const filteredHits = hits.length > 3 ? hits.filter((hit) => hit._score! >= hits[2]._score!) : hits; diff --git a/src/geotextSearch/DAL/queries.ts b/src/location/DAL/queries.ts similarity index 66% rename from src/geotextSearch/DAL/queries.ts rename to src/location/DAL/queries.ts index f700acc5..4d41b827 100644 --- a/src/geotextSearch/DAL/queries.ts +++ b/src/location/DAL/queries.ts @@ -1,9 +1,12 @@ +/* eslint-disable @typescript-eslint/no-unused-expressions */ /* eslint-disable @typescript-eslint/naming-convention */ import WKT, { GeoJSONPolygon } from 'wellknown'; import { estypes } from '@elastic/elasticsearch'; import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { TextSearchParams } from '../interfaces'; -import { IApplication } from '../../common/interfaces'; +import { GeoContextMode, IApplication } from '../../common/interfaces'; +import { BadRequestError } from '../../common/errors'; +import { geoContextQuery } from '../../common/utils'; const TEXT_FIELD = 'text'; const PLACETYPE_FIELD = 'placetype.keyword'; @@ -15,10 +18,26 @@ const HIERARCHY_FIELD = 'heirarchy'; const PLACETYPE_SEARCH_FIELD = 'sub_placetype_keyword'; export const geotextQuery = ( - { query, limit: size, name, placeTypes, subPlaceTypes, hierarchies, regions, viewbox, boundary, sources }: TextSearchParams, + { + query, + limit: size, + name, + placeTypes, + subPlaceTypes, + hierarchies, + region, + source, + geoContext, + geoContextMode, + disableFuzziness, + }: TextSearchParams, textLanguage: string, boosts: IApplication['elasticQueryBoosts'] ): estypes.SearchRequest => { + if ((geoContext !== undefined && geoContextMode === undefined) || (geoContext === undefined && geoContextMode !== undefined)) { + throw new BadRequestError('/location/geotextQuery: geo_context and geo_context_mode must be both defined or both undefined'); + } + const esQuery: estypes.SearchRequest = { query: { function_score: { @@ -37,6 +56,19 @@ export const geotextQuery = ( size, }; + if (geoContext && geoContextMode) { + const geoContextQueryFilter = geoContextQuery(geoContext, GeoContextMode.FILTER, GEOJSON_FIELD).filter![0]; + + if (geoContextMode === GeoContextMode.FILTER) { + (esQuery.query?.function_score?.query?.bool?.filter as QueryDslQueryContainer[]).push(geoContextQueryFilter); + } else { + esQuery.query?.function_score?.functions?.push({ + weight: boosts.viewbox, + filter: geoContextQueryFilter, + }); + } + } + if (!name && subPlaceTypes?.length) { (esQuery.query?.function_score?.query?.bool?.must as QueryDslQueryContainer[]).push( ...[ @@ -57,32 +89,23 @@ export const geotextQuery = ( match: { [TEXT_FIELD]: { query, - fuzziness: 'AUTO:3,4', + fuzziness: disableFuzziness ? undefined : 'AUTO:3,4', }, }, }); } - boundary && - (esQuery.query?.function_score?.query?.bool?.filter as QueryDslQueryContainer[]).push({ - geo_shape: { - [GEOJSON_FIELD]: { - shape: boundary, - }, - }, - }); - - sources?.length && + source?.length && (esQuery.query?.function_score?.query?.bool?.filter as QueryDslQueryContainer[]).push({ terms: { - [SOURCE_FIELD]: sources, + [SOURCE_FIELD]: source.map((s) => s.toLowerCase()), }, }); - regions?.length && + region?.length && (esQuery.query?.function_score?.query?.bool?.filter as QueryDslQueryContainer[]).push({ terms: { - [REGION_FIELD]: regions, + [REGION_FIELD]: region.map((r) => r.toLowerCase()), }, }); @@ -116,18 +139,6 @@ export const geotextQuery = ( }, }); - viewbox && - esQuery.query?.function_score?.functions?.push({ - weight: boosts.viewbox, - filter: { - geo_shape: { - [GEOJSON_FIELD]: { - shape: viewbox, - }, - }, - }, - }); - hierarchies.forEach((hierarchy) => { const hierarchyShape = typeof hierarchy.geo_json === 'string' ? WKT.parse(hierarchy.geo_json) : hierarchy.geo_json; esQuery.query?.function_score?.functions?.push({ @@ -150,14 +161,14 @@ export const geotextQuery = ( return esQuery; }; -export const placetypeQuery = (query: string): estypes.SearchRequest => ({ +export const placetypeQuery = (query: string, disableFuzziness: boolean): estypes.SearchRequest => ({ query: { bool: { should: { match: { [PLACETYPE_SEARCH_FIELD]: { query, - fuzziness: 'AUTO:3,4', + fuzziness: disableFuzziness ? undefined : 'AUTO:3,4', }, }, }, @@ -166,7 +177,7 @@ export const placetypeQuery = (query: string): estypes.SearchRequest => ({ size: 2, }); -export const hierarchyQuery = (query: string): estypes.SearchRequest => ({ +export const hierarchyQuery = (query: string, disableFuzziness: boolean): estypes.SearchRequest => ({ query: { function_score: { functions: [ @@ -176,7 +187,7 @@ export const hierarchyQuery = (query: string): estypes.SearchRequest => ({ match: { [HIERARCHY_FIELD]: { query, - fuzziness: 'AUTO:3,4', + fuzziness: disableFuzziness ? undefined : 'AUTO:3,4', }, }, }, @@ -188,7 +199,7 @@ export const hierarchyQuery = (query: string): estypes.SearchRequest => ({ match: { text: { query, - fuzziness: 'AUTO:3,4', + fuzziness: disableFuzziness ? undefined : 'AUTO:3,4', }, }, }, diff --git a/src/geotextSearch/controllers/geotextSearchController.ts b/src/location/controllers/locationController.ts similarity index 57% rename from src/geotextSearch/controllers/geotextSearchController.ts rename to src/location/controllers/locationController.ts index f8b776d7..491c06b5 100644 --- a/src/geotextSearch/controllers/geotextSearchController.ts +++ b/src/location/controllers/locationController.ts @@ -2,20 +2,24 @@ import { Logger } from '@map-colonies/js-logger'; import { BoundCounter, Meter } from '@opentelemetry/api-metrics'; import { RequestHandler } from 'express'; import httpStatus from 'http-status-codes'; +import { Feature } from 'geojson'; import { injectable, inject } from 'tsyringe'; import { SERVICES } from '../../common/constants'; -import { GeotextSearchManager } from '../models/geotextSearchManager'; -import { GetGeotextSearchParams, QueryResult } from '../interfaces'; +import { GeotextSearchManager } from '../models/locationManager'; +import { GetGeotextSearchParams } from '../interfaces'; +import { GenericGeocodingResponse } from '../../common/interfaces'; type GetGeotextSearchHandler = RequestHandler< unknown, - QueryResult | { message: string; error: string }, //response + GenericGeocodingResponse | { message: string; error: string }, //response undefined, GetGeotextSearchParams >; type GetRegionshHandler = RequestHandler; +type GetSourcesHandler = RequestHandler; + @injectable() export class GeotextSearchController { private readonly createdResourceCounter: BoundCounter; @@ -29,8 +33,18 @@ export class GeotextSearchController { } public getGeotextSearch: GetGeotextSearchHandler = async (req, res, next) => { + const { + disable_fuzziness: disableFuzziness, + geo_context: geoContext, + geo_context_mode: geoContextMode, + query, + region, + source, + limit, + } = req.query; + try { - const response = await this.manager.search(req.query); + const response = await this.manager.search({ query, region, source, disableFuzziness, geoContext, geoContextMode, limit }); return res.status(httpStatus.OK).json(response); } catch (error: unknown) { this.logger.error('Error in getGeotextSearch', error); @@ -38,7 +52,11 @@ export class GeotextSearchController { } }; - public getRegions: GetRegionshHandler = (req, res, next) => { + public getRegions: GetRegionshHandler = (req, res) => { return res.status(httpStatus.OK).json(this.manager.regions()); }; + + public getSources: GetSourcesHandler = (req, res) => { + return res.status(httpStatus.OK).json(this.manager.sources()); + }; } diff --git a/src/location/interfaces.ts b/src/location/interfaces.ts new file mode 100644 index 00000000..87d6f138 --- /dev/null +++ b/src/location/interfaces.ts @@ -0,0 +1,28 @@ +import type { GeoJSON } from 'geojson'; +import { estypes } from '@elastic/elasticsearch'; +import { CommonRequestParameters } from '../common/interfaces'; +import { ConvertCamelToSnakeCase, ConvertSnakeToCamelCase, RemoveUnderscore } from '../common/utils'; +import { HierarchySearchHit } from './models/elasticsearchHits'; + +export interface PlaceType { + placetype: string; + confidence: number; +} + +export interface TokenResponse { + tokens: string[]; + prediction: string[]; +} + +export interface TextSearchParams extends ConvertSnakeToCamelCase { + name?: string; + placeTypes?: string[]; + subPlaceTypes?: string[]; + hierarchies: HierarchySearchHit[]; +} + +export interface GetGeotextSearchParams extends CommonRequestParameters { + query: string; + source?: string[]; + region?: string[]; +} diff --git a/src/geotextSearch/models/elasticsearchHits.ts b/src/location/models/elasticsearchHits.ts similarity index 100% rename from src/geotextSearch/models/elasticsearchHits.ts rename to src/location/models/elasticsearchHits.ts diff --git a/src/geotextSearch/models/geotextSearchManager.ts b/src/location/models/locationManager.ts similarity index 74% rename from src/geotextSearch/models/geotextSearchManager.ts rename to src/location/models/locationManager.ts index 1626406e..867656a7 100644 --- a/src/geotextSearch/models/geotextSearchManager.ts +++ b/src/location/models/locationManager.ts @@ -1,12 +1,14 @@ import { IConfig } from 'config'; import { Logger } from '@map-colonies/js-logger'; import { inject, injectable } from 'tsyringe'; +import { Feature } from 'geojson'; import { SERVICES, elasticConfigPath } from '../../common/constants'; -import { GEOTEXT_REPOSITORY_SYMBOL, GeotextRepository } from '../DAL/geotextSearchRepository'; -import { GetGeotextSearchParams, QueryResult, TextSearchParams } from '../interfaces'; -import { convertResult, parseGeo } from '../utils'; -import { IApplication } from '../../common/interfaces'; +import { GEOTEXT_REPOSITORY_SYMBOL, GeotextRepository } from '../DAL/locationRepository'; +import { GetGeotextSearchParams, TextSearchParams } from '../interfaces'; +import { convertResult } from '../utils'; +import { GenericGeocodingResponse, IApplication } from '../../common/interfaces'; import { ElasticDbClientsConfig } from '../../common/elastic/interfaces'; +import { ConvertSnakeToCamelCase } from '../../common/utils'; @injectable() export class GeotextSearchManager { @@ -17,7 +19,7 @@ export class GeotextSearchManager { @inject(GEOTEXT_REPOSITORY_SYMBOL) private readonly geotextRepository: GeotextRepository ) {} - public async search(params: GetGeotextSearchParams): Promise { + public async search(params: ConvertSnakeToCamelCase): Promise> { const extractNameEndpoint = this.appConfig.services.tokenTypesUrl; const { geotext: geotextIndex, @@ -32,23 +34,18 @@ export class GeotextSearchManager { const promises = Promise.all([ this.geotextRepository.extractName(extractNameEndpoint, query), - this.geotextRepository.generatePlacetype(placetypesIndex, query), - this.geotextRepository.extractHierarchy(hierarchiesIndex, hierarchyQuery.join(','), hierarchyBoost), + this.geotextRepository.generatePlacetype(placetypesIndex, query, params.disableFuzziness), + this.geotextRepository.extractHierarchy(hierarchiesIndex, hierarchyQuery.join(','), hierarchyBoost, params.disableFuzziness), ]); const [name, { placeTypes, subPlaceTypes }, hierarchies] = await promises; const searchParams: TextSearchParams = { - query, - limit: params.limit, - sources: params.source ? (params.source instanceof Array ? params.source : [params.source]) : undefined, - viewbox: params.viewbox ? parseGeo(params.viewbox) : undefined, - boundary: params.boundary ? parseGeo(params.boundary) : undefined, + ...params, name, placeTypes, subPlaceTypes, hierarchies, - regions: params.region, }; const esResult = await this.geotextRepository.geotextSearch( @@ -58,7 +55,7 @@ export class GeotextSearchManager { this.appConfig.elasticQueryBoosts ); - return convertResult(searchParams, esResult.hits.hits, { + return convertResult(searchParams, esResult, { sources: this.appConfig.sources, regionCollection: this.appConfig.regions, nameKeys: this.appConfig.nameTranslationsKeys, @@ -69,4 +66,8 @@ export class GeotextSearchManager { public regions(): string[] { return Object.keys(this.appConfig.regions ?? {}); } + + public sources(): string[] { + return Object.keys(this.appConfig.sources ?? {}); + } } diff --git a/src/geotextSearch/parsing.ts b/src/location/parsing.ts similarity index 55% rename from src/geotextSearch/parsing.ts rename to src/location/parsing.ts index 29172a94..99657aa9 100644 --- a/src/geotextSearch/parsing.ts +++ b/src/location/parsing.ts @@ -1,18 +1,17 @@ +//This file might be deprecated in the future +/* istanbul ignore file */ +import { SearchHit } from '@elastic/elasticsearch/lib/api/types'; import { XMLParser } from 'fast-xml-parser'; -type Highlight = { - em: string | string[]; - '#text': string; -}; const HIERARCHY_OF_INTEREST = 3; const HIGHLIGHT_XML_REGEX = /|<\/em>/gi; -const untagHighlight = (highlight: string) => highlight.replace(HIGHLIGHT_XML_REGEX, ''); +const untagHighlight = (highlight: string): string => highlight.replace(HIGHLIGHT_XML_REGEX, ''); -const calculateHighlightQuality = (highlight: string, queryWordCount: number) => { +const calculateHighlightQuality = (highlight: string, queryWordCount: number): number => { const parser = new XMLParser({ numberParseOptions: { skipLike: /[0-9]+/, hex: false, leadingZeros: false } }); - const parsed = parser.parse(highlight) as Highlight; + const parsed = parser.parse(highlight) as NonNullable; if (!(parsed.em instanceof Array)) { parsed.em = [parsed.em]; @@ -32,18 +31,18 @@ const compareQualityThenLength = ( highlight: string; quality: number; } -) => a.quality - b.quality || a.highlight.length - b.highlight.length; +): number => a.quality - b.quality || a.highlight.length - b.highlight.length; -export const generateDisplayName = (highlights: string[], queryWordCount: number, name?: string) => { +export const generateDisplayName = ({ text: highlights }: NonNullable, queryWordCount: number, name?: string): string => { const scored = highlights.map((highlight) => ({ highlight, quality: calculateHighlightQuality(highlight, queryWordCount), })); - const filtered = scored.filter(name ? ({ highlight }) => highlight.includes(name) : ({ quality }) => quality < 1); + const filtered = scored.filter(name !== undefined ? ({ highlight }): boolean => highlight.includes(name) : ({ quality }): boolean => quality < 1); - const chosen = (filtered.length ? filtered : scored).sort(compareQualityThenLength).pop()?.highlight; + const chosen = (filtered.length ? filtered : scored).sort(compareQualityThenLength).pop()!.highlight; - return chosen && untagHighlight(chosen); + return untagHighlight(chosen); }; export const getHierarchyOfInterest = (hierarchy: string): string => hierarchy.split('/')[HIERARCHY_OF_INTEREST]; diff --git a/src/geotextSearch/routes/geotextSearchRouter.ts b/src/location/routes/locationRouter.ts similarity index 72% rename from src/geotextSearch/routes/geotextSearchRouter.ts rename to src/location/routes/locationRouter.ts index 2c72e165..430e42fe 100644 --- a/src/geotextSearch/routes/geotextSearchRouter.ts +++ b/src/location/routes/locationRouter.ts @@ -1,13 +1,14 @@ import { Router } from 'express'; import { FactoryFunction } from 'tsyringe'; -import { GeotextSearchController } from '../controllers/geotextSearchController'; +import { GeotextSearchController } from '../controllers/locationController'; const geotextSearchRouterFactory: FactoryFunction = (dependencyContainer) => { const router = Router(); const controller = dependencyContainer.resolve(GeotextSearchController); router.get('/regions', controller.getRegions); - router.get('/', controller.getGeotextSearch); + router.get('/query', controller.getGeotextSearch); + router.get('/sources', controller.getSources); return router; }; diff --git a/src/location/utils.ts b/src/location/utils.ts new file mode 100644 index 00000000..559d7770 --- /dev/null +++ b/src/location/utils.ts @@ -0,0 +1,107 @@ +import https from 'https'; +import { Feature, Geometry } from 'geojson'; +import { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import { StatusCodes } from 'http-status-codes'; +import axios, { AxiosError, AxiosResponse as Response } from 'axios'; +import { InternalServerError } from '../common/errors'; +import { GenericGeocodingResponse, IApplication } from '../common/interfaces'; +import { TextSearchParams } from './interfaces'; +import { TextSearchHit } from './models/elasticsearchHits'; +import { generateDisplayName } from './parsing'; + +const FIND_QUOTES = /["']/g; + +const FIND_SPECIAL = /[`!@#$%^&*()_\-+=|\\/,.<>:[\]{}\n\t\r\s;؛]+/g; + +const axiosInstance = axios.create({ + httpsAgent: new https.Agent({ rejectUnauthorized: false }), +}); + +export const fetchNLPService = async (endpoint: string, requestData: object): Promise => { + let res: Response | null = null, + data: T[] | undefined | null = null; + try { + res = await axiosInstance.post(endpoint, requestData); + } catch (err: unknown) { + throw new InternalServerError(`NLP analyser is not available - ${(err as AxiosError).message}`); + } + + data = res?.data as T[] | undefined; + + if (res?.status !== StatusCodes.OK || !data || data.length < 1 || !data[0]) { + throw new InternalServerError(`NLP analyser unexpected response: ${JSON.stringify(data)}`); + } + return data; +}; + +export const cleanQuery = (query: string): string[] => query.replace(FIND_QUOTES, '').split(FIND_SPECIAL); + +/* eslint-disable @typescript-eslint/naming-convention */ +export const convertResult = ( + params: TextSearchParams, + results: SearchResponse, + { + sources, + regionCollection, + nameKeys, + mainLanguageRegex, + }: { + sources?: IApplication['sources']; + regionCollection?: IApplication['regions']; + nameKeys: IApplication['nameTranslationsKeys']; + mainLanguageRegex: IApplication['mainLanguageRegex']; + } = { nameKeys: [], mainLanguageRegex: '' } +): GenericGeocodingResponse => ({ + type: 'FeatureCollection', + geocoding: { + version: process.env.npm_package_version, + query: { + query: params.query, + region: params.region, + source: params.source, + geo_context: params.geoContext, + geo_context_mode: params.geoContextMode, + disable_fuzziness: params.disableFuzziness, + limit: params.limit, + }, + response: { + results_count: results.hits.hits.length, + max_score: results.hits.max_score ?? 0, + match_latency_ms: results.took, + name: params.name ?? undefined, + place_types: params.placeTypes, + sub_place_types: params.subPlaceTypes, + hierarchies: params.hierarchies, + }, + }, + features: results.hits.hits.map(({ _source: feature, _score: score, highlight }, index): GenericGeocodingResponse['features'][number] => { + const allNames = [feature!.text, feature!.translated_text || []]; + return { + type: 'Feature', + geometry: feature!.geo_json as Geometry, + properties: { + score, + matches: [ + { + layer: feature!.layer_name, + source: (sources ?? {})[feature?.source ?? ''] ?? feature?.source, + source_id: feature?.source_id.map((id) => id.replace(/(^\{)|(\}$)/g, '')) ?? [], + }, + ], + names: { + [nameKeys[0]]: new RegExp(mainLanguageRegex).test(feature!.text[0]) ? allNames.shift() : allNames.pop(), + [nameKeys[1]]: allNames.pop(), + ['default']: [feature!.name], + display: highlight ? generateDisplayName(highlight, params.query.split(' ').length, params.name) : feature!.name, + }, + placetype: feature?.placetype, // TODO: check if to remove this + sub_placetype: feature?.sub_placetype, + regions: feature?.region.map((region) => ({ + region: region, + sub_region_names: feature.sub_region.filter((sub_region) => (regionCollection ?? {})[region ?? '']?.includes(sub_region)), + })), + }, + }; + }), +}); +/* eslint-enable @typescript-eslint/naming-convention */ diff --git a/src/mgrs/controllers/mgrsController.ts b/src/mgrs/controllers/mgrsController.ts new file mode 100644 index 00000000..c482fc51 --- /dev/null +++ b/src/mgrs/controllers/mgrsController.ts @@ -0,0 +1,48 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Logger } from '@map-colonies/js-logger'; +import { BoundCounter, Meter } from '@opentelemetry/api-metrics'; +import { RequestHandler } from 'express'; +import { Feature } from 'geojson'; +import httpStatus from 'http-status-codes'; +import { injectable, inject } from 'tsyringe'; +import { SERVICES } from '../../common/constants'; +import { MgrsManager } from '../models/mgrsManager'; + +type GetTilesHandler = RequestHandler< + undefined, + | Feature + | { + type: string; + message: string; + }, + undefined, + GetTileQueryParams +>; + +export interface GetTileQueryParams { + tile: string; +} + +@injectable() +export class MgrsController { + private readonly createdResourceCounter: BoundCounter; + + public constructor( + @inject(SERVICES.LOGGER) private readonly logger: Logger, + @inject(MgrsManager) private readonly manager: MgrsManager, + @inject(SERVICES.METER) private readonly meter: Meter + ) { + this.createdResourceCounter = meter.createCounter('created_resource'); + } + + public getTile: GetTilesHandler = (req, res, next) => { + try { + const { tile } = req.query; + const response = this.manager.getTile({ tile }); + return res.status(httpStatus.OK).json(response); + } catch (error: unknown) { + this.logger.error(`MgrsController.getTile Error: ${(error as Error).message}`); + next(error); + } + }; +} diff --git a/src/mgrs/models/mgrsManager.ts b/src/mgrs/models/mgrsManager.ts new file mode 100644 index 00000000..b340b5d6 --- /dev/null +++ b/src/mgrs/models/mgrsManager.ts @@ -0,0 +1,54 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { IConfig } from 'config'; +import { Logger } from '@map-colonies/js-logger'; +import { BBox, Feature, Geometry } from 'geojson'; +import { inject, injectable } from 'tsyringe'; +import * as mgrs from 'mgrs'; +import { SERVICES } from '../../common/constants'; +import { IApplication } from '../../common/interfaces'; +import { GetTileQueryParams } from '../controllers/mgrsController'; +import { BadRequestError } from '../../common/errors'; +import { parseGeo } from '../../common/utils'; + +@injectable() +export class MgrsManager { + public constructor( + @inject(SERVICES.LOGGER) private readonly logger: Logger, + @inject(SERVICES.CONFIG) private readonly config: IConfig, + @inject(SERVICES.APPLICATION) private readonly application: IApplication + ) {} + + public getTile({ tile }: GetTileQueryParams): Feature & { geocoding: { [key: string]: unknown } } { + let bbox: BBox | undefined; + try { + bbox = mgrs.inverse(tile); + } catch (error) { + if ((error as Error).message.includes('MGRSPoint bad conversion')) { + throw new BadRequestError('Invalid MGRS tile'); + } + this.logger.error(`Failed to convert MGRS tile to bbox. Error: ${(error as Error).message}`); + throw error; + } + + const geometry = parseGeo({ bbox }) as Geometry; + + return { + type: 'Feature', + geocoding: { + query: { + tile, + }, + response: { + max_score: 1, + results_count: 1, + match_latency_ms: 0, + }, + }, + bbox, + geometry, + properties: { + score: 1, + }, + }; + } +} diff --git a/src/mgrs/routers/mgrsRouter.ts b/src/mgrs/routers/mgrsRouter.ts new file mode 100644 index 00000000..77ac0714 --- /dev/null +++ b/src/mgrs/routers/mgrsRouter.ts @@ -0,0 +1,16 @@ +import { Router } from 'express'; +import { FactoryFunction } from 'tsyringe'; +import { MgrsController } from '../controllers/mgrsController'; + +const mgrsRouterFactory: FactoryFunction = (dependencyContainer) => { + const router = Router(); + const controller = dependencyContainer.resolve(MgrsController); + + router.get('/tiles', controller.getTile); + + return router; +}; + +export const MGRS_ROUTER_SYMBOL = Symbol('mgrsRouterFactory'); + +export { mgrsRouterFactory }; diff --git a/src/route/DAL/queries.ts b/src/route/DAL/queries.ts deleted file mode 100644 index c07f5309..00000000 --- a/src/route/DAL/queries.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { estypes } from '@elastic/elasticsearch'; -import { boundingBox, geoDistance } from '../../common/elastic/utils'; -import { GeoContext, WGS84Coordinate } from '../../common/interfaces'; - -export interface RouteQueryParams { - commandName: string; - controlPoint?: number; - geo?: GeoContext; -} - -/* eslint-disable @typescript-eslint/naming-convention */ -export const queryForRoute = (params: RouteQueryParams): estypes.SearchRequest => ({ - query: { - bool: { - should: [ - { - term: { - 'properties.TYPE.keyword': 'ROUTE', - }, - }, - ], - must: [ - { - match: { - 'properties.OBJECT_COMMAND_NAME.keyword': { - query: params.commandName, - fuzziness: 1, - prefix_length: 1, - }, - }, - }, - ...(params.geo?.bbox ? [boundingBox(params.geo.bbox)] : []), - ...(params.geo?.radius - ? [ - geoDistance( - params.geo as { - radius: number; - lon: number; - lat: number; - } - ), - ] - : []), - ], - }, - }, -}); - -export const queryForControlPointInRoute = (params: RouteQueryParams & Required>): estypes.SearchRequest => ({ - query: { - bool: { - must: [ - { - match: { - 'properties.OBJECT_COMMAND_NAME.keyword': { - query: params.controlPoint, - fuzziness: 1, - prefix_length: 1, - }, - }, - }, - ...(params.geo?.bbox ? [boundingBox(params.geo.bbox)] : []), - ...(params.geo?.radius ?? 0 ? [geoDistance(params.geo as WGS84Coordinate & { radius: number })] : []), - ], - filter: [ - { - terms: { - 'properties.LAYER_NAME.keyword': ['CONTROL_GIL_GDB.CTR_CONTROL_POINT_N', 'CONTROL_GIL_GDB.CTR_CONTROL_POINT_CROSS_N'], - }, - }, - { - term: { - 'properties.TIED_TO': params.commandName, - }, - }, - ], - }, - }, -}); diff --git a/src/route/models/route.ts b/src/route/models/route.ts deleted file mode 100644 index 4860b131..00000000 --- a/src/route/models/route.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Feature } from 'geojson'; - -export interface Route extends Feature {} diff --git a/src/route/models/routeManager.ts b/src/route/models/routeManager.ts deleted file mode 100644 index 619b9a69..00000000 --- a/src/route/models/routeManager.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { IConfig } from 'config'; -import { Logger } from '@map-colonies/js-logger'; -import { inject, injectable } from 'tsyringe'; -import { estypes } from '@elastic/elasticsearch'; -import { SERVICES } from '../../common/constants'; -import { ROUTE_REPOSITORY_SYMBOL, RouteRepository } from '../DAL/routeRepository'; -import { RouteQueryParams } from '../DAL/queries'; -import { formatResponse } from '../../common/utils'; -import { FeatureCollection } from '../../common/interfaces'; -import { getElasticClientQuerySize } from '../../common/elastic/utils'; -import { Route } from './route'; - -@injectable() -export class RouteManager { - public constructor( - @inject(SERVICES.LOGGER) private readonly logger: Logger, - @inject(SERVICES.CONFIG) private readonly config: IConfig, - @inject(ROUTE_REPOSITORY_SYMBOL) private readonly routeRepository: RouteRepository - ) {} - - public async getRoutes(routeQueryParams: RouteQueryParams, reduceFuzzyMatch = false, size?: number): Promise> { - let elasticResponse: estypes.SearchResponse | undefined = undefined; - if (routeQueryParams.controlPoint ?? 0) { - elasticResponse = await this.routeRepository.getControlPointInRoute( - routeQueryParams as RouteQueryParams & Required>, - size ?? getElasticClientQuerySize(this.config, 'control') - ); - } else { - elasticResponse = await this.routeRepository.getRoutes(routeQueryParams, size ?? getElasticClientQuerySize(this.config, 'control')); - } - - const formattedResponse = formatResponse(elasticResponse); - - if (reduceFuzzyMatch && formattedResponse.features.length > 0) { - const filterFunction = - routeQueryParams.controlPoint ?? 0 - ? (hit: Route | undefined): hit is Route => hit?.properties?.OBJECT_COMMAND_NAME === routeQueryParams.controlPoint - : (hit: Route | undefined): hit is Route => hit?.properties?.OBJECT_COMMAND_NAME === routeQueryParams.commandName; - formattedResponse.features = formattedResponse.features.filter(filterFunction); - } - - return formattedResponse; - } -} diff --git a/src/serverBuilder.ts b/src/serverBuilder.ts index 9e8b387c..6a533d37 100644 --- a/src/serverBuilder.ts +++ b/src/serverBuilder.ts @@ -10,12 +10,14 @@ import httpLogger from '@map-colonies/express-access-log-middleware'; import { defaultMetricsMiddleware, getTraceContexHeaderMiddleware } from '@map-colonies/telemetry'; import { SERVICES } from './common/constants'; import { IConfig } from './common/interfaces'; -import { TILE_ROUTER_SYMBOL } from './tile/routes/tileRouter'; -import { ITEM_ROUTER_SYMBOL } from './item/routes/itemRouter'; -import { ROUTE_ROUTER_SYMBOL } from './route/routes/routeRouter'; +import { TILE_ROUTER_SYMBOL } from './control/tile/routes/tileRouter'; +import { ITEM_ROUTER_SYMBOL } from './control/item/routes/itemRouter'; +import { ROUTE_ROUTER_SYMBOL } from './control/route/routes/routeRouter'; import { LAT_LON_ROUTER_SYMBOL } from './latLon/routes/latLonRouter'; -import { GEOTEXT_SEARCH_ROUTER_SYMBOL } from './geotextSearch/routes/geotextSearchRouter'; +import { GEOTEXT_SEARCH_ROUTER_SYMBOL } from './location/routes/locationRouter'; import { cronLoadTileLatLonDataSymbol } from './latLon/DAL/latLonDAL'; +import { FeedbackApiMiddlewareManager } from './common/middlewares/feedbackApi.middleware'; +import { MGRS_ROUTER_SYMBOL } from './mgrs/routers/mgrsRouter'; @injectable() export class ServerBuilder { @@ -29,17 +31,18 @@ export class ServerBuilder { @inject(ROUTE_ROUTER_SYMBOL) private readonly routeRouter: Router, @inject(LAT_LON_ROUTER_SYMBOL) private readonly latLonRouter: Router, @inject(GEOTEXT_SEARCH_ROUTER_SYMBOL) private readonly geotextRouter: Router, - @inject(cronLoadTileLatLonDataSymbol) private readonly cronLoadTileLatLonData: void + @inject(cronLoadTileLatLonDataSymbol) private readonly cronLoadTileLatLonData: void, + @inject(FeedbackApiMiddlewareManager) private readonly feedbackApiMiddleware: FeedbackApiMiddlewareManager, + @inject(MGRS_ROUTER_SYMBOL) private readonly mgrsRouter: Router ) { this.serverInstance = express(); // eslint-disable-next-line @typescript-eslint/no-unused-expressions - this.cronLoadTileLatLonData; } public build(): express.Application { this.registerPreRoutesMiddleware(); this.buildDocsRoutes(); - this.buildRoutesV1(); + this.buildRoutes(); this.registerPostRoutesMiddleware(); return this.serverInstance; @@ -54,14 +57,27 @@ export class ServerBuilder { this.serverInstance.use(this.config.get('openapiConfig.basePath'), openapiRouter.getRouter()); } - private buildRoutesV1(): void { + private buildRoutes(): void { + this.serverInstance.use(this.feedbackApiMiddleware.saveResponses); const router = Router(); - router.use('/search/tiles', this.tileRouter); - router.use('/search/items', this.itemRouter); - router.use('/search/routes', this.routeRouter); + router.use('/lookup', this.latLonRouter); - router.use('/query', this.geotextRouter); - this.serverInstance.use('/v1', router); + router.use('/location', this.geotextRouter); + router.use('/control', this.buildControlRoutes()); + router.use('/MGRS', this.mgrsRouter); + + this.serverInstance.use('/search', router); + this.serverInstance.use('/lookup', this.latLonRouter); + } + + private buildControlRoutes(): Router { + const router = Router(); + + router.use('/tiles', this.tileRouter); + router.use('/items', this.itemRouter); + router.use('/routes', this.routeRouter); + + return router; } private registerPreRoutesMiddleware(): void { @@ -80,6 +96,8 @@ export class ServerBuilder { const apiSpecPath = this.config.get('openapiConfig.filePath'); this.serverInstance.use(OpenApiMiddleware({ apiSpec: apiSpecPath, validateRequests: true, ignorePaths: ignorePathRegex })); this.serverInstance.disable('x-powered-by'); + + this.serverInstance.use(this.feedbackApiMiddleware.setRequestId); } private registerPostRoutesMiddleware(): void { diff --git a/src/tile/DAL/queries.ts b/src/tile/DAL/queries.ts deleted file mode 100644 index d3dfeb92..00000000 --- a/src/tile/DAL/queries.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { estypes } from '@elastic/elasticsearch'; - -export interface TileQueryParams { - tile: string; - subTile?: number; -} - -export const queryForTiles = (params: Omit): estypes.SearchRequest => ({ - query: { - bool: { - must: [ - { - term: { - 'properties.TYPE.keyword': 'TILE', - }, - }, - { - match: { - 'properties.TILE_NAME.keyword': { - query: params.tile, - fuzziness: 1, - prefix_length: 1, - }, - }, - }, - ], - }, - }, -}); - -export const queryForSubTiles = (params: Required): estypes.SearchRequest => ({ - query: { - bool: { - must: [ - { - term: { - 'properties.TYPE.keyword': 'SUB_TILE', - }, - }, - { - term: { - 'properties.TILE_NAME.keyword': params.tile, - }, - }, - { - match: { - 'properties.SUB_TILE_ID.keyword': params.subTile, - }, - }, - ], - }, - }, -}); diff --git a/src/tile/DAL/tileRepository.ts b/src/tile/DAL/tileRepository.ts deleted file mode 100644 index 1b1de8b3..00000000 --- a/src/tile/DAL/tileRepository.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Logger } from '@map-colonies/js-logger'; -import { estypes } from '@elastic/elasticsearch'; -import { FactoryFunction } from 'tsyringe'; -import { ElasticClient, ElasticClients } from '../../common/elastic'; -import { Tile } from '../models/tile'; -import { additionalControlSearchProperties, queryElastic } from '../../common/elastic/utils'; -import { IConfig } from '../../common/interfaces'; -import { SERVICES } from '../../common/constants'; -import { queryForTiles, queryForSubTiles, TileQueryParams } from './queries'; - -// eslint-disable-next-line @typescript-eslint/explicit-function-return-type -const createTileRepository = (client: ElasticClient, config: IConfig, logger: Logger) => { - return { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - async getTiles(tileQueryParams: TileQueryParams, size: number): Promise> { - const response = await queryElastic(client, { ...additionalControlSearchProperties(config, size), ...queryForTiles(tileQueryParams) }); - - return response; - }, - - async getSubTiles(tileQueryParams: Required, size: number): Promise> { - const response = await queryElastic(client, { ...additionalControlSearchProperties(config, size), ...queryForSubTiles(tileQueryParams) }); - - return response; - }, - }; -}; - -export type TileRepository = ReturnType; - -export const tileRepositoryFactory: FactoryFunction = (depContainer) => { - return createTileRepository( - depContainer.resolve(SERVICES.ELASTIC_CLIENTS).control, - depContainer.resolve(SERVICES.CONFIG), - depContainer.resolve(SERVICES.LOGGER) - ); -}; - -export const TILE_REPOSITORY_SYMBOL = Symbol('TILE_REPOSITORY_SYMBOL'); diff --git a/src/tile/models/tile.ts b/src/tile/models/tile.ts deleted file mode 100644 index 91cc6452..00000000 --- a/src/tile/models/tile.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Feature } from 'geojson'; - -export interface Tile extends Feature {} diff --git a/src/tile/models/tileManager.ts b/src/tile/models/tileManager.ts deleted file mode 100644 index 121f4aec..00000000 --- a/src/tile/models/tileManager.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { IConfig } from 'config'; -import { Logger } from '@map-colonies/js-logger'; -import { inject, injectable } from 'tsyringe'; -import { estypes } from '@elastic/elasticsearch'; -import { SERVICES } from '../../common/constants'; -import { TILE_REPOSITORY_SYMBOL, TileRepository } from '../DAL/tileRepository'; -import { formatResponse } from '../../common/utils'; -import { TileQueryParams } from '../DAL/queries'; -import { FeatureCollection } from '../../common/interfaces'; -import { getElasticClientQuerySize } from '../../common/elastic/utils'; -import { Tile } from './tile'; - -@injectable() -export class TileManager { - public constructor( - @inject(SERVICES.LOGGER) private readonly logger: Logger, - @inject(SERVICES.CONFIG) private readonly config: IConfig, - @inject(TILE_REPOSITORY_SYMBOL) private readonly tileRepository: TileRepository - ) {} - - public async getTiles(tileQueryParams: TileQueryParams, reduceFuzzyMatch = false, size?: number): Promise> { - let elasticResponse: estypes.SearchResponse | undefined = undefined; - const numberOfResults = size ?? getElasticClientQuerySize(this.config, 'control'); - if (tileQueryParams.subTile ?? 0) { - elasticResponse = await this.tileRepository.getSubTiles(tileQueryParams as Required, numberOfResults); - } else { - elasticResponse = await this.tileRepository.getTiles(tileQueryParams, numberOfResults); - } - - const formattedResponse = formatResponse(elasticResponse); - - if (reduceFuzzyMatch && formattedResponse.features.length > 0) { - const filterFunction = - tileQueryParams.subTile ?? 0 - ? (hit: Tile | undefined): hit is Tile => - hit?.properties?.SUB_TILE_ID === tileQueryParams.subTile && hit?.properties?.TILE_NAME === tileQueryParams.tile - : (hit: Tile | undefined): hit is Tile => hit?.properties?.TILE_NAME === tileQueryParams.tile; - formattedResponse.features = formattedResponse.features.filter(filterFunction); - } - - return formattedResponse; - } -} diff --git a/tests/configurations/integration/jest.config.js b/tests/configurations/integration/jest.config.js index 3ae834e9..673b558e 100644 --- a/tests/configurations/integration/jest.config.js +++ b/tests/configurations/integration/jest.config.js @@ -11,6 +11,8 @@ module.exports = { testMatch: ['/tests/integration/**/*.spec.ts'], setupFiles: ['/tests/configurations/jest.setup.ts'], setupFilesAfterEnv: ['jest-openapi', '/tests/configurations/initJestOpenapi.setup.ts'], + globalSetup: '/tests/integration/globalSetup.ts', + globalTeardown: '/tests/integration/globalTeardown.ts', reporters: [ 'default', [ diff --git a/tests/configurations/unit/jest.config.js b/tests/configurations/unit/jest.config.js index 07af12e5..bfd03fc4 100644 --- a/tests/configurations/unit/jest.config.js +++ b/tests/configurations/unit/jest.config.js @@ -13,6 +13,7 @@ module.exports = { '!*/common/**', '!**/controllers/**', '!**/routes/**', + '!**/redis/**', '!/src/*', ], coverageDirectory: '/coverage', diff --git a/tests/integration/item/helpers/requestSender.ts b/tests/integration/control/item/helpers/requestSender.ts similarity index 70% rename from tests/integration/item/helpers/requestSender.ts rename to tests/integration/control/item/helpers/requestSender.ts index 28f460fa..78a17c20 100644 --- a/tests/integration/item/helpers/requestSender.ts +++ b/tests/integration/control/item/helpers/requestSender.ts @@ -1,5 +1,5 @@ import * as supertest from 'supertest'; -import { GetItemsQueryParams } from '../../../../src/item/controllers/itemController'; +import { GetItemsQueryParams } from '../../../../../src/control/item/controllers/itemController'; export class ItemRequestSender { public constructor(private readonly app: Express.Application) {} @@ -7,9 +7,9 @@ export class ItemRequestSender { public async getItems(queryParams?: GetItemsQueryParams): Promise { return supertest .agent(this.app) - .get('/v1/search/items/') + .get('/search/control/items') .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') + .set('x-api-key', 'abc123') .set('x-user-id', 'abc123') .query(queryParams ?? {}); } diff --git a/tests/integration/control/item/item.spec.ts b/tests/integration/control/item/item.spec.ts new file mode 100644 index 00000000..0fad0366 --- /dev/null +++ b/tests/integration/control/item/item.spec.ts @@ -0,0 +1,448 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import jsLogger from '@map-colonies/js-logger'; +import { trace } from '@opentelemetry/api'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; +import { Application } from 'express'; +import { DependencyContainer } from 'tsyringe'; +import httpStatusCodes from 'http-status-codes'; +import { getApp } from '../../../../src/app'; +import { SERVICES } from '../../../../src/common/constants'; +import { GetItemsQueryParams } from '../../../../src/control/item/controllers/itemController'; +import { Item } from '../../../../src/control/item/models/item'; +import { CommonRequestParameters, GenericGeocodingResponse, GeoContext, GeoContextMode } from '../../../../src/common/interfaces'; +import { cronLoadTileLatLonDataSymbol } from '../../../../src/latLon/DAL/latLonDAL'; +import { S3_REPOSITORY_SYMBOL } from '../../../../src/common/s3/s3Repository'; +import { expectedResponse } from '../utils'; +import { ItemRequestSender } from './helpers/requestSender'; +import { ITEM_1234, ITEM_1235, ITEM_1236 } from './mockObjects'; + +describe('/search/control/items', function () { + let requestSender: ItemRequestSender; + let app: { app: Application; container: DependencyContainer }; + + beforeEach(async function () { + app = await getApp({ + override: [ + { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, + { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { token: S3_REPOSITORY_SYMBOL, provider: { useValue: {} } }, + { token: SERVICES.S3_CLIENT, provider: { useValue: {} } }, + { token: cronLoadTileLatLonDataSymbol, provider: { useValue: {} } }, + ], + useChild: true, + }); + + requestSender = new ItemRequestSender(app.app); + }); + + afterAll(async function () { + const cleanupRegistry = app.container.resolve(SERVICES.CLEANUP_REGISTRY); + await cleanupRegistry.trigger(); + app.container.reset(); + + jest.clearAllTimers(); + }); + + describe('Happy Path', function () { + it('should return 200 status code and items', async function () { + const requestParams: GetItemsQueryParams = { command_name: '123', limit: 5, disable_fuzziness: false }; + + const response = await requestSender.getItems(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1234, ITEM_1235, ITEM_1236], expect) + ); + }); + + it('should return 200 status code and items in tile RIT', async function () { + const requestParams: GetItemsQueryParams = { command_name: '123', tile: 'RIT', limit: 5, disable_fuzziness: false }; + + const response = await requestSender.getItems(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1234, ITEM_1235, ITEM_1236], expect) + ); + }); + + it('should return 200 status code and no items in tile RIC', async function () { + const requestParams: GetItemsQueryParams = { command_name: '123', tile: 'RIC', limit: 5, disable_fuzziness: false }; + + const response = await requestSender.getItems(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [], expect) + ); + }); + + it('should return 200 status code and tiles biased by geo_context (bbox)', async function () { + const requestParams: GetItemsQueryParams = { + command_name: '123', + geo_context: { bbox: [12.507611205446722, 41.90406708449768, 12.517586703397825, 41.90966573813128] }, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getItems(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1236, ITEM_1234, ITEM_1235], expect) + ); + }); + + it('should return 200 status code and tiles filtered by geo_context (bbox)', async function () { + const requestParams: GetItemsQueryParams = { + command_name: '123', + geo_context: { bbox: [12.507611205446722, 41.90406708449768, 12.517586703397825, 41.90966573813128] }, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getItems(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1236], expect) + ); + }); + + it('should return 200 status code and tiles biased by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.512345456194254, lat: 41.90673389969385, radius: 10 }; + + const requestParams: GetItemsQueryParams = { + command_name: '123', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getItems({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetItemsQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1236, ITEM_1234, ITEM_1235], expect) + ); + }); + + it('should return 200 status code and tiles filtered by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.512345456194254, lat: 41.90673389969385, radius: 10 }; + const requestParams: GetItemsQueryParams = { + command_name: '123', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getItems({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetItemsQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1236], expect) + ); + }); + + it('should return 200 status code and tiles biased by geo_context (UTM)', async function () { + const geo_context = { x: 293671, y: 4642414, zone: 33, radius: 10 }; + const requestParams: GetItemsQueryParams = { + command_name: '123', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getItems({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetItemsQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1236, ITEM_1234, ITEM_1235], expect) + ); + }); + + it('should return 200 status code and tiles filtered by geo_context (UTM)', async function () { + const geo_context = { x: 293671, y: 4642414, zone: 33, radius: 10 }; + const requestParams: GetItemsQueryParams = { + command_name: '123', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getItems({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetItemsQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1236], expect) + ); + }); + + it('should return 200 status code and send 1234 item as disable_fuzziness is true', async function () { + const requestParams: GetItemsQueryParams = { + command_name: '1234', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getItems(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1234], expect) + ); + }); + + it('should return 200 status code and no item as disable_fuzziness is true', async function () { + const requestParams: GetItemsQueryParams = { + command_name: '123', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getItems(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [], expect) + ); + }); + + it('should return 200 status code and items in sub_tile', async function () { + const requestParams: GetItemsQueryParams = { + command_name: '123', + sub_tile: '37', + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getItems(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ITEM_1234, ITEM_1235], expect) + ); + }); + }); + describe('Bad Path', function () { + // All requests with status code of 400 + it("should return 400 status code and error message when item's command_name", async function () { + const response = await requestSender.getItems({} as unknown as GetItemsQueryParams); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: "request/query must have required property 'command_name'", + }); + }); + + it('should return 400 status code and error message when command_name value is empty', async function () { + const response = await requestSender.getItems({ command_name: '', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: "Empty value found for query parameter 'command_name'", + }); + }); + + it('should return 400 status code and error message when tile value is invalid', async function () { + let response = await requestSender.getItems({ command_name: '1234', tile: 'invalid', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: 'request/query/tile must NOT have more than 3 characters', + }); + + response = await requestSender.getItems({ command_name: '1234', tile: 'i', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: 'request/query/tile must NOT have fewer than 3 characters', + }); + }); + + test.each(['invalid', '6a6', '06', '-11', '6 ', ' 6', ' ', ' 6 ', ''])( + 'should return 400 status code and error message when sub_tile value is invalid', + async (sub_tile) => { + const response = await requestSender.getItems({ command_name: '1234', tile: 'RIT', sub_tile, limit: 5, disable_fuzziness: false }); + + const message = sub_tile ? 'request/query/sub_tile must match pattern "^[1-9][0-9]*$"' : "Empty value found for query parameter 'sub_tile'"; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + } + ); + + test.each>([ + { + geo_context: { x: 300850, y: 4642203, zone: 33, radius: 100 }, + }, + { + geo_context: { lon: 12.598899687444742, lat: 41.90667824634701, radius: 10 }, + }, + { + geo_context: { bbox: [12.554407132912445, 41.84962590648513, 12.652837919839953, 41.94545380230761] }, + }, + { + geo_context_mode: GeoContextMode.BIAS, + }, + { + geo_context_mode: GeoContextMode.FILTER, + }, + ])('should return 400 and message that geo_context and geo_context_mode must be both defined or both undefined', async function (requestParams) { + const response = await requestSender.getItems({ command_name: '1234', limit: 5, disable_fuzziness: false, ...requestParams }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: '/control/utils/geoContextQuery: geo_context and geo_context_mode must be both defined or both undefined', + }); + }); + + test.each>([ + { + geo_context: { x: 300850, y: 4642203, zone: 33, radius: 100 }, + }, + { + geo_context: { lon: 12.598899687444742, lat: 41.90667824634701, radius: 10 }, + }, + { + geo_context: { bbox: [12.554407132912445, 41.84962590648513, 12.652837919839953, 41.94545380230761] }, + }, + { + geo_context_mode: GeoContextMode.BIAS, + }, + { + geo_context_mode: GeoContextMode.FILTER, + }, + ])('should return 400 and message that geo_context and geo_context_mode must be both defined or both undefined', async function (requestParams) { + const response = await requestSender.getItems({ command_name: '1234', limit: 5, disable_fuzziness: false, ...requestParams }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: '/control/utils/geoContextQuery: geo_context and geo_context_mode must be both defined or both undefined', + }); + }); + + test.each>([ + { + disable_fuzziness: '' as unknown as boolean, + }, + { + disable_fuzziness: 'True' as unknown as boolean, + }, + { + disable_fuzziness: 'False' as unknown as boolean, + }, + ])('should return 400 status code and error message when disable_fuzziness value is invalid', async function (requestParams) { + const response = await requestSender.getItems({ command_name: '1234', limit: 5, ...requestParams }); + + const message = + (requestParams.disable_fuzziness as unknown as string) === '' + ? "Empty value found for query parameter 'disable_fuzziness'" + : 'request/query/disable_fuzziness must be boolean'; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + }); + + test.each>([ + { + limit: '' as unknown as number, + }, + { + limit: 0, + }, + { + limit: 101, + }, + ])('should return 400 status code and error message when limit value is invalid', async function (requestParams) { + const response = await requestSender.getItems({ command_name: '1234', disable_fuzziness: false, ...requestParams }); + + const message = + (requestParams.limit as unknown as string) === '' + ? "Empty value found for query parameter 'limit'" + : requestParams.limit < 1 + ? 'request/query/limit must be >= 1' + : 'request/query/limit must be <= 15'; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + }); + + test.each<(keyof GeoContext)[][]>([[['lat', 'lon', 'radius']], [['x', 'y', 'zone', 'radius']]])( + 'should return 400 for all invalid geo_context object for the keys %s', + async function (keys) { + function generateCombinations(keys: (keyof GeoContext)[]): GeoContext[] { + const combinations: object[] = []; + + function backtrack(current: object, remainingKeys: string[]): void { + if (remainingKeys.length === 0) { + combinations.push(current); + return; + } + + const key = remainingKeys[0]; + const remaining = remainingKeys.slice(1); + + backtrack({ ...current, [key]: 1 }, remaining); + backtrack(current, remaining); + } + + backtrack({}, keys); + + return combinations; + } + + const geoContexts = generateCombinations(keys); + + for (const geo_context of geoContexts) { + if (Object.keys(geo_context).length === keys.length) { + continue; + } + const response = await requestSender.getItems({ + command_name: '1234', + limit: 5, + disable_fuzziness: false, + geo_context: JSON.stringify(geo_context) as unknown as GetItemsQueryParams['geo_context'], + geo_context_mode: GeoContextMode.BIAS, + }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: + 'geo_context validation: geo_context must contain one of the following: {"bbox": [number,number,number,number] | [number,number,number,number,number,number]}, {"lat": number, "lon": number, "radius": number}, or {"x": number, "y": number, "zone": number, "radius": number}', + }); + } + } + ); + }); + describe('Sad Path', function () { + // All requests with status code 4XX-5XX + }); +}); diff --git a/tests/integration/control/item/mockObjects.ts b/tests/integration/control/item/mockObjects.ts new file mode 100644 index 00000000..96a62178 --- /dev/null +++ b/tests/integration/control/item/mockObjects.ts @@ -0,0 +1,99 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-magic-numbers */ +import { Item } from '../../../../src/control/item/models/item'; + +export const ITEM_1234: Item = { + type: 'Feature', + geometry: { + coordinates: [ + [ + [12.432792582620323, 41.9327692706986], + [12.432648028923637, 41.93209008126263], + [12.43295235249525, 41.93189198298137], + [12.435105441764364, 41.93235609798671], + [12.435516278586334, 41.93274663116725], + [12.43637599267521, 41.93308056343986], + [12.436026020567567, 41.93386161853829], + [12.432792582620323, 41.9327692706986], + ], + ], + type: 'Polygon', + }, + properties: { + LAYER_NAME: 'CONTROL.ITEMS', + OBJECT_COMMAND_NAME: '1234', + TILE_NAME: 'RIT', + SUB_TILE_ID: '37', + ENTITY_HEB: 'hotel', + TYPE: 'ITEM', + }, +}; + +export const ITEM_1235: Item = { + type: 'Feature', + geometry: { + coordinates: [ + [ + [12.454224879037952, 41.93533128513971], + [12.4536294370983, 41.93493944442386], + [12.453354617740985, 41.934479454425286], + [12.453308814515196, 41.93386612926582], + [12.453789748389482, 41.933065390320394], + [12.454660009686364, 41.93255427509931], + [12.455667680662373, 41.93265649847106], + [12.456217319376208, 41.933355020461335], + [12.456217319376208, 41.934309086916585], + [12.45569058227531, 41.935092773686364], + [12.454957730656645, 41.93534832163783], + [12.454224879037952, 41.93533128513971], + ], + ], + type: 'Polygon', + }, + properties: { + LAYER_NAME: 'CONTROL.ITEMS', + OBJECT_COMMAND_NAME: '1235', + TILE_NAME: 'RIT', + SUB_TILE_ID: '37', + ENTITY_HEB: 'oplympic stadium', + TYPE: 'ITEM', + }, +}; + +export const ITEM_1236: Item = { + type: 'Feature', + geometry: { + coordinates: [ + [ + [12.508591887289015, 41.908719199148294], + [12.5109416268715, 41.90414674778023], + [12.515619548794433, 41.905558627360875], + [12.514541686600694, 41.906697734719614], + [12.514929038978266, 41.9065233923005], + [12.515546733877699, 41.90711121610428], + [12.51436197480865, 41.90815873484965], + [12.513612640013008, 41.90876915151631], + [12.512974692823434, 41.90872393566693], + [12.512863305218872, 41.90807583830912], + [12.513561316395936, 41.90754911551221], + [12.513962675013943, 41.907221129768516], + [12.51419876831929, 41.906986853205694], + [12.513954805237745, 41.906893142339726], + [12.512516460769376, 41.90804005137895], + [12.511855399516293, 41.90857302052004], + [12.51176096219396, 41.908660872149284], + [12.508762577222939, 41.908924426310676], + [12.508591887289015, 41.908719199148294], + ], + ], + type: 'Polygon', + }, + properties: { + LAYER_NAME: 'CONTROL.ITEMS', + OBJECT_COMMAND_NAME: '1236', + TILE_NAME: 'RIT', + SUB_TILE_ID: '38', + ENTITY_HEB: 'hospital', + TYPE: 'ITEM', + }, +}; diff --git a/tests/integration/route/helpers/requestSender.ts b/tests/integration/control/route/helpers/requestSender.ts similarity index 69% rename from tests/integration/route/helpers/requestSender.ts rename to tests/integration/control/route/helpers/requestSender.ts index adac30a7..d5dd4b68 100644 --- a/tests/integration/route/helpers/requestSender.ts +++ b/tests/integration/control/route/helpers/requestSender.ts @@ -1,5 +1,5 @@ import * as supertest from 'supertest'; -import { GetRoutesQueryParams } from '../../../../src/route/controllers/routeController'; +import { GetRoutesQueryParams } from '../../../../../src/control/route/controllers/routeController'; export class RouteRequestSender { public constructor(private readonly app: Express.Application) {} @@ -7,9 +7,9 @@ export class RouteRequestSender { public async getRoutes(queryParams?: GetRoutesQueryParams): Promise { return supertest .agent(this.app) - .get('/v1/search/routes/') + .get('/search/control/routes') .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') + .set('x-api-key', 'abc123') .set('x-user-id', 'abc123') .query(queryParams ?? {}); } diff --git a/tests/integration/control/route/mockObjects.ts b/tests/integration/control/route/mockObjects.ts new file mode 100644 index 00000000..eb60440b --- /dev/null +++ b/tests/integration/control/route/mockObjects.ts @@ -0,0 +1,75 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-magic-numbers */ +import { Route } from '../../../../src/control/route/models/route'; + +export const ROUTE_VIA_CAMILLUCCIA_A: Route = { + type: 'Feature', + geometry: { + coordinates: [ + [12.443243654365062, 41.93890891937724], + [12.442636325462843, 41.93804302794496], + [12.442828646282095, 41.93725242115204], + [12.443405608739027, 41.93556576056804], + [12.44388471483822, 41.93370245333628], + [12.445132826188399, 41.93084467089824], + [12.445812354584433, 41.93031849813403], + [12.445819951137906, 41.930296776658196], + ], + type: 'LineString', + }, + properties: { + OBJECT_COMMAND_NAME: 'via camillucciaA', + ENTITY_HEB: 'route', + TYPE: 'ROUTE', + LAYER_NAME: 'CONTROL.ROUTES', + }, +}; +export const ROUTE_VIA_CAMILLUCCIA_B: Route = { + type: 'Feature', + geometry: { + coordinates: [ + [12.445818466287847, 41.93029376141277], + [12.446047161911423, 41.930040913942264], + [12.446171038707206, 41.9297762500957], + [12.446167862379838, 41.92945014491775], + [12.446075748864388, 41.9286136066203], + ], + type: 'LineString', + }, + properties: { + OBJECT_COMMAND_NAME: 'via camillucciaB', + ENTITY_HEB: 'route', + TYPE: 'ROUTE', + LAYER_NAME: 'CONTROL.ROUTES', + }, +}; + +export const CONTROL_POINT_OLIMPIADE_111: Route = { + type: 'Feature', + geometry: { + coordinates: [12.475638293442415, 41.932360642739155], + type: 'Point', + }, + properties: { + OBJECT_COMMAND_NAME: '111', + ENTITY_HEB: 'control point', + TIED_TO: 'olimpiade', + TYPE: 'ITEM' as never, + LAYER_NAME: 'CONTROL_GIL_GDB.CTR_CONTROL_POINT_CROSS_N', + }, +}; + +export const CONTROL_POINT_OLIMPIADE_112: Route = { + type: 'Feature', + geometry: { + coordinates: [12.474175672012962, 41.932217551210556], + type: 'Point', + }, + properties: { + OBJECT_COMMAND_NAME: '112', + ENTITY_HEB: 'control point', + TIED_TO: 'olimpiade', + TYPE: 'ITEM' as never, + LAYER_NAME: 'CONTROL_GIL_GDB.CTR_CONTROL_POINT_CROSS_N', + }, +}; diff --git a/tests/integration/control/route/route.spec.ts b/tests/integration/control/route/route.spec.ts new file mode 100644 index 00000000..5b117c7a --- /dev/null +++ b/tests/integration/control/route/route.spec.ts @@ -0,0 +1,565 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import jsLogger from '@map-colonies/js-logger'; +import { trace } from '@opentelemetry/api'; +import httpStatusCodes from 'http-status-codes'; +import { Application } from 'express'; +import { DependencyContainer } from 'tsyringe'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; +import { getApp } from '../../../../src/app'; +import { SERVICES } from '../../../../src/common/constants'; +import { GetRoutesQueryParams } from '../../../../src/control/route/controllers/routeController'; +import { Route } from '../../../../src/control/route/models/route'; +import { CommonRequestParameters, GenericGeocodingResponse, GeoContext, GeoContextMode } from '../../../../src/common/interfaces'; +import { S3_REPOSITORY_SYMBOL } from '../../../../src/common/s3/s3Repository'; +import { cronLoadTileLatLonDataSymbol } from '../../../../src/latLon/DAL/latLonDAL'; +import { expectedResponse } from '../utils'; +import { RouteRequestSender } from './helpers/requestSender'; +import { ROUTE_VIA_CAMILLUCCIA_A, ROUTE_VIA_CAMILLUCCIA_B, CONTROL_POINT_OLIMPIADE_111, CONTROL_POINT_OLIMPIADE_112 } from './mockObjects'; + +describe('/search/control/route', function () { + let requestSender: RouteRequestSender; + let app: { app: Application; container: DependencyContainer }; + + beforeEach(async function () { + app = await getApp({ + override: [ + { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, + { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { token: S3_REPOSITORY_SYMBOL, provider: { useValue: {} } }, + { token: SERVICES.S3_CLIENT, provider: { useValue: {} } }, + { token: cronLoadTileLatLonDataSymbol, provider: { useValue: {} } }, + ], + useChild: true, + }); + + requestSender = new RouteRequestSender(app.app); + }); + + afterAll(async function () { + const cleanupRegistry = app.container.resolve(SERVICES.CLEANUP_REGISTRY); + await cleanupRegistry.trigger(); + app.container.reset(); + + jest.clearAllTimers(); + }); + + describe('Happy Path', function () { + it('should return 200 status code and routes', async function () { + const requestParams: GetRoutesQueryParams = { command_name: 'via camilluccia', limit: 5, disable_fuzziness: false }; + + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ROUTE_VIA_CAMILLUCCIA_A, ROUTE_VIA_CAMILLUCCIA_B], expect) + ); + }); + + it('should return 200 status code and routes biased by geo_context (bbox)', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'via camilluccia', + geo_context: { bbox: [12.445945519411595, 41.92899524904075, 12.446385440853476, 41.9292587345808] }, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ROUTE_VIA_CAMILLUCCIA_B, ROUTE_VIA_CAMILLUCCIA_A], expect) + ); + }); + + it('should return 200 status code and routes filtered by geo_context (bbox)', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'via camilluccia', + geo_context: { bbox: [12.445945519411595, 41.92899524904075, 12.446385440853476, 41.9292587345808] }, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ROUTE_VIA_CAMILLUCCIA_B], expect) + ); + }); + + it('should return 200 status code and routes biased by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.446085277848027, lat: 41.928658505847835, radius: 10 }; + const requestParams: GetRoutesQueryParams = { + command_name: 'via camilluccia', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ROUTE_VIA_CAMILLUCCIA_B, ROUTE_VIA_CAMILLUCCIA_A], expect) + ); + }); + + it('should return 200 status code and routes filtered by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.446085277848027, lat: 41.928658505847835, radius: 10 }; + const requestParams: GetRoutesQueryParams = { + command_name: 'via camilluccia', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ROUTE_VIA_CAMILLUCCIA_B], expect) + ); + }); + + it('should return 200 status code and routes biased by geo_context (UTM)', async function () { + const geo_context = { x: 288247, y: 4645010, zone: 33, radius: 10 }; + const requestParams: GetRoutesQueryParams = { + command_name: 'via camilluccia', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ROUTE_VIA_CAMILLUCCIA_B, ROUTE_VIA_CAMILLUCCIA_A], expect) + ); + }); + + it('should return 200 status code and routes filtered by geo_context (UTM)', async function () { + const geo_context = { x: 288247, y: 4645010, zone: 33, radius: 10 }; + const requestParams: GetRoutesQueryParams = { + command_name: 'via camilluccia', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ROUTE_VIA_CAMILLUCCIA_B], expect) + ); + }); + + it('should return 200 status code and send via camillucciaB route as disable_fuzziness is true', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'via camillucciaB', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [ROUTE_VIA_CAMILLUCCIA_B], expect) + ); + }); + + it('should return 200 status code and no route as disable_fuzziness is true', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'via camilluccia', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [], expect) + ); + }); + + it('should return 200 status code and control points biased by geo_context (bbox)', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'olimpiade', + control_point: '11', + geo_context: { bbox: [12.473743746822265, 41.93195262135879, 12.474626229200709, 41.93249150688004] }, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [CONTROL_POINT_OLIMPIADE_112, CONTROL_POINT_OLIMPIADE_111], expect) + ); + }); + + it('should return 200 status code and control points filtered by geo_context (bbox)', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'olimpiade', + control_point: '11', + geo_context: { bbox: [12.473743746822265, 41.93195262135879, 12.474626229200709, 41.93249150688004] }, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [CONTROL_POINT_OLIMPIADE_112], expect) + ); + }); + + it('should return 200 status code and control points biased by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.474175672012962, lat: 41.932217551210556, radius: 10 }; + const requestParams: GetRoutesQueryParams = { + command_name: 'olimpiade', + control_point: '11', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [CONTROL_POINT_OLIMPIADE_112, CONTROL_POINT_OLIMPIADE_111], expect) + ); + }); + + it('should return 200 status code and control points filtered by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.474175672012962, lat: 41.932217551210556, radius: 10 }; + const requestParams: GetRoutesQueryParams = { + command_name: 'olimpiade', + control_point: '11', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [CONTROL_POINT_OLIMPIADE_112], expect) + ); + }); + + it('should return 200 status code and control points biased by geo_context (UTM)', async function () { + const geo_context = { x: 290588, y: 4645336, zone: 33, radius: 10 }; + const requestParams: GetRoutesQueryParams = { + command_name: 'olimpiade', + control_point: '11', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [CONTROL_POINT_OLIMPIADE_112, CONTROL_POINT_OLIMPIADE_111], expect) + ); + }); + + it('should return 200 status code and control points filtered by geo_context (UTM)', async function () { + const geo_context = { x: 290588, y: 4645336, zone: 33, radius: 10 }; + const requestParams: GetRoutesQueryParams = { + command_name: 'olimpiade', + control_point: '11', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [CONTROL_POINT_OLIMPIADE_112], expect) + ); + }); + + it('should return 200 status code and control points', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'olimpiade', + control_point: '111', + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [CONTROL_POINT_OLIMPIADE_111, CONTROL_POINT_OLIMPIADE_112], expect) + ); + }); + + it('should return 200 status code and 111 control_point with disable_fuzziness true', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'olimpiade', + control_point: '111', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [CONTROL_POINT_OLIMPIADE_111], expect) + ); + }); + + it('should return 200 status code and return no control points as no control points with disable_fuzziness true', async function () { + const requestParams: GetRoutesQueryParams = { + command_name: 'camilluccia', + control_point: '111', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getRoutes(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [], expect) + ); + }); + }); + describe('Bad Path', function () { + // All requests with status code of 400 + it('should return 400 status code and error message when empty object is passed', async function () { + const response = await requestSender.getRoutes({} as unknown as GetRoutesQueryParams); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: "request/query must have required property 'command_name'", + }); + }); + + it('should return 400 status code and error message when command_name is empty', async function () { + const response = await requestSender.getRoutes({ command_name: '', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: "Empty value found for query parameter 'command_name'", + }); + }); + + test.each(['invalid', '6a6', '06', '-11', '6 ', ' 6', ' ', ' 6 ', ''])( + 'should return 400 status code and error message when value: "%s" for control_point value is invalid', + async (control_point) => { + const response = await requestSender.getRoutes({ command_name: 'olimpiade', control_point, limit: 5, disable_fuzziness: false }); + + const message = control_point + ? 'request/query/control_point must match pattern "^[1-9][0-9]*$"' + : "Empty value found for query parameter 'control_point'"; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + } + ); + + test.each>([ + { + geo_context: { x: 300850, y: 4642203, zone: 33, radius: 100 }, + }, + { + geo_context: { lon: 12.598899687444742, lat: 41.90667824634701, radius: 10 }, + }, + { + geo_context: { bbox: [12.554407132912445, 41.84962590648513, 12.652837919839953, 41.94545380230761] }, + }, + { + geo_context_mode: GeoContextMode.BIAS, + }, + { + geo_context_mode: GeoContextMode.FILTER, + }, + ])('should return 400 and message that geo_context and geo_context_mode must be both defined or both undefined', async function (requestParams) { + const response = await requestSender.getRoutes({ command_name: '1234', limit: 5, disable_fuzziness: false, ...requestParams }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: '/control/utils/geoContextQuery: geo_context and geo_context_mode must be both defined or both undefined', + }); + }); + + test.each>([ + { + geo_context: { x: 300850, y: 4642203, zone: 33, radius: 100 }, + }, + { + geo_context: { lon: 12.598899687444742, lat: 41.90667824634701, radius: 10 }, + }, + { + geo_context: { bbox: [12.554407132912445, 41.84962590648513, 12.652837919839953, 41.94545380230761] }, + }, + { + geo_context_mode: GeoContextMode.BIAS, + }, + { + geo_context_mode: GeoContextMode.FILTER, + }, + ])('should return 400 and message that geo_context and geo_context_mode must be both defined or both undefined', async function (requestParams) { + const response = await requestSender.getRoutes({ command_name: '1234', limit: 5, disable_fuzziness: false, ...requestParams }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: '/control/utils/geoContextQuery: geo_context and geo_context_mode must be both defined or both undefined', + }); + }); + + test.each>([ + { + disable_fuzziness: '' as unknown as boolean, + }, + { + disable_fuzziness: 'True' as unknown as boolean, + }, + { + disable_fuzziness: 'False' as unknown as boolean, + }, + ])('should return 400 status code and error message when disable_fuzziness value is invalid', async function (requestParams) { + const response = await requestSender.getRoutes({ command_name: '1234', limit: 5, ...requestParams }); + + const message = + (requestParams.disable_fuzziness as unknown as string) === '' + ? "Empty value found for query parameter 'disable_fuzziness'" + : 'request/query/disable_fuzziness must be boolean'; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + }); + + test.each>([ + { + limit: '' as unknown as number, + }, + { + limit: 0, + }, + { + limit: 101, + }, + ])('should return 400 status code and error message when limit value is invalid', async function (requestParams) { + const response = await requestSender.getRoutes({ command_name: '1234', disable_fuzziness: false, ...requestParams }); + + const message = + (requestParams.limit as unknown as string) === '' + ? "Empty value found for query parameter 'limit'" + : requestParams.limit < 1 + ? 'request/query/limit must be >= 1' + : 'request/query/limit must be <= 15'; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + }); + + test.each<(keyof GeoContext)[][]>([[['lat', 'lon', 'radius']], [['x', 'y', 'zone', 'radius']]])( + 'should return 400 for all invalid geo_context object for the keys %s', + async function (keys) { + function generateCombinations(keys: (keyof GeoContext)[]): GeoContext[] { + const combinations: object[] = []; + + function backtrack(current: object, remainingKeys: string[]): void { + if (remainingKeys.length === 0) { + combinations.push(current); + return; + } + + const key = remainingKeys[0]; + const remaining = remainingKeys.slice(1); + + backtrack({ ...current, [key]: 1 }, remaining); + backtrack(current, remaining); + } + + backtrack({}, keys); + + return combinations; + } + + const geoContexts = generateCombinations(keys); + + for (const geo_context of geoContexts) { + if (Object.keys(geo_context).length === keys.length) { + continue; + } + const response = await requestSender.getRoutes({ + command_name: '1234', + limit: 5, + disable_fuzziness: false, + geo_context: JSON.stringify(geo_context) as unknown as GetRoutesQueryParams['geo_context'], + geo_context_mode: GeoContextMode.BIAS, + }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: + 'geo_context validation: geo_context must contain one of the following: {"bbox": [number,number,number,number] | [number,number,number,number,number,number]}, {"lat": number, "lon": number, "radius": number}, or {"x": number, "y": number, "zone": number, "radius": number}', + }); + } + } + ); + }); + describe('Sad Path', function () { + // All requests with status code 4XX-5XX + }); +}); diff --git a/tests/integration/tile/helpers/requestSender.ts b/tests/integration/control/tile/helpers/requestSender.ts similarity index 70% rename from tests/integration/tile/helpers/requestSender.ts rename to tests/integration/control/tile/helpers/requestSender.ts index 0eaed8de..d15778c1 100644 --- a/tests/integration/tile/helpers/requestSender.ts +++ b/tests/integration/control/tile/helpers/requestSender.ts @@ -1,5 +1,5 @@ import * as supertest from 'supertest'; -import { GetTilesQueryParams } from '../../../../src/tile/controllers/tileController'; +import { GetTilesQueryParams } from '../../../../../src/control/tile/controllers/tileController'; export class TileRequestSender { public constructor(private readonly app: Express.Application) {} @@ -7,9 +7,9 @@ export class TileRequestSender { public async getTiles(queryParams?: GetTilesQueryParams): Promise { return supertest .agent(this.app) - .get('/v1/search/tiles/') + .get('/search/control/tiles') .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') + .set('x-api-key', 'abc123') .set('x-user-id', 'abc123') .query(queryParams ?? {}); } diff --git a/tests/integration/control/tile/mockObjects.ts b/tests/integration/control/tile/mockObjects.ts new file mode 100644 index 00000000..2292a2cd --- /dev/null +++ b/tests/integration/control/tile/mockObjects.ts @@ -0,0 +1,85 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-magic-numbers */ +import { Tile } from '../../../../src/control/tile/models/tile'; + +export const RIT_TILE: Tile = { + type: 'Feature', + geometry: { + coordinates: [ + [ + [12.539507865186607, 41.851751203650096], + [12.536787075186538, 41.94185043165008], + [12.42879133518656, 41.93952837265009], + [12.431625055186686, 41.84943698365008], + [12.539507865186607, 41.851751203650096], + ], + ], + type: 'Polygon', + }, + properties: { + TILE_NAME: 'RIT', + TYPE: 'TILE', + }, +}; + +export const RIC_TILE: Tile = { + type: 'Feature', + properties: { + TILE_NAME: 'RIC', + TYPE: 'TILE', + }, + geometry: { + coordinates: [ + [ + [12.64750356570994, 41.854073129598774], + [12.64478277570987, 41.94417235759876], + [12.536787035709892, 41.941850298598766], + [12.539620755710018, 41.85175890959876], + [12.64750356570994, 41.854073129598774], + ], + ], + type: 'Polygon', + }, +}; + +export const SUB_TILE_66: Tile = { + type: 'Feature', + properties: { + SUB_TILE_ID: '66', + TILE_NAME: 'RIT', + TYPE: 'SUB_TILE', + }, + geometry: { + coordinates: [ + [ + [12.44999804325252, 41.930226156898485], + [12.45011422425253, 41.939247112898514], + [12.439626097252557, 41.93934663789851], + [12.439510908252515, 41.93032564689851], + [12.44999804325252, 41.930226156898485], + ], + ], + type: 'Polygon', + }, +}; + +export const SUB_TILE_65: Tile = { + type: 'Feature', + properties: { + SUB_TILE_ID: '65', + TILE_NAME: 'RIT', + TYPE: 'SUB_TILE', + }, + geometry: { + coordinates: [ + [ + [12.439530324602458, 41.93031190061167], + [12.439646505602468, 41.9393328566117], + [12.429158378602494, 41.939432381611695], + [12.429043189602453, 41.930411390611695], + [12.439530324602458, 41.93031190061167], + ], + ], + type: 'Polygon', + }, +}; diff --git a/tests/integration/control/tile/tile.spec.ts b/tests/integration/control/tile/tile.spec.ts new file mode 100644 index 00000000..c44d6967 --- /dev/null +++ b/tests/integration/control/tile/tile.spec.ts @@ -0,0 +1,580 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import jsLogger from '@map-colonies/js-logger'; +import { trace } from '@opentelemetry/api'; +import { DependencyContainer } from 'tsyringe'; +import { Application } from 'express'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; +import httpStatusCodes from 'http-status-codes'; +import { BBox } from 'geojson'; +import { getApp } from '../../../../src/app'; +import { SERVICES } from '../../../../src/common/constants'; +import { GetTilesQueryParams } from '../../../../src/control/tile/controllers/tileController'; +import { Tile } from '../../../../src/control/tile/models/tile'; +import { CommonRequestParameters, GenericGeocodingResponse, GeoContext, GeoContextMode } from '../../../../src/common/interfaces'; +import { S3_REPOSITORY_SYMBOL } from '../../../../src/common/s3/s3Repository'; +import { cronLoadTileLatLonDataSymbol } from '../../../../src/latLon/DAL/latLonDAL'; +import { expectedResponse } from '../utils'; +import { TileRequestSender } from './helpers/requestSender'; +import { RIC_TILE, RIT_TILE, SUB_TILE_65, SUB_TILE_66 } from './mockObjects'; + +describe('/search/control/tiles', function () { + let requestSender: TileRequestSender; + let app: { app: Application; container: DependencyContainer }; + + beforeEach(async function () { + app = await getApp({ + override: [ + { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, + { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { token: S3_REPOSITORY_SYMBOL, provider: { useValue: {} } }, + { token: SERVICES.S3_CLIENT, provider: { useValue: {} } }, + { token: cronLoadTileLatLonDataSymbol, provider: { useValue: {} } }, + ], + useChild: true, + }); + + requestSender = new TileRequestSender(app.app); + }); + + afterAll(async function () { + const cleanupRegistry = app.container.resolve(SERVICES.CLEANUP_REGISTRY); + await cleanupRegistry.trigger(); + app.container.reset(); + + jest.clearAllTimers(); + }); + + describe('Happy Path', function () { + it('should return 200 status code and tiles', async function () { + const requestParams: GetTilesQueryParams = { tile: 'RIT', limit: 5, disable_fuzziness: false }; + + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIT_TILE, RIC_TILE], expect) + ); + }); + + it('should return 200 status code and tiles biased by geo_context (bbox)', async function () { + const requestParams: GetTilesQueryParams = { + tile: 'RI', + geo_context: { bbox: [12.554407132912445, 41.84962590648513, 12.652837919839953, 41.94545380230761] }, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE, RIT_TILE], expect) + ); + }); + + it('should return 200 status code and tiles filtered by geo_context (bbox)', async function () { + const requestParams: GetTilesQueryParams = { + tile: 'RI', + geo_context: { bbox: [12.554407132912445, 41.84962590648513, 12.652837919839953, 41.94545380230761] }, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE], expect) + ); + }); + + it('should return 200 status code and tiles biased by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.598899687444742, lat: 41.90667824634701, radius: 10 }; + const requestParams: GetTilesQueryParams = { + tile: 'RI', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE, RIT_TILE], expect) + ); + }); + + it('should return 200 status code and tiles filtered by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.598899687444742, lat: 41.90667824634701, radius: 10 }; + const requestParams: GetTilesQueryParams = { + tile: 'RI', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE], expect) + ); + }); + + it('should return 200 status code and tiles biased by geo_context (UTM)', async function () { + const geo_context = { x: 300850, y: 4642203, zone: 33, radius: 100 }; + const requestParams: GetTilesQueryParams = { + tile: 'RI', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE, RIT_TILE], expect) + ); + }); + + it('should return 200 status code and tiles filtered by geo_context (UTM)', async function () { + const geo_context = { x: 300850, y: 4642203, zone: 33, radius: 100 }; + const requestParams: GetTilesQueryParams = { + tile: 'RI', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE], expect) + ); + }); + + it('should return 200 status code and send RIC tile as disable_fuzziness is true', async function () { + const requestParams: GetTilesQueryParams = { + tile: 'RIC', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE], expect) + ); + }); + + it('should return 200 status code and no tile as disable_fuzziness is true', async function () { + const requestParams: GetTilesQueryParams = { + tile: 'RI', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [], expect) + ); + }); + + it('should return 200 status code and sub tiles', async function () { + const requestParams: GetTilesQueryParams = { + tile: 'RIT', + sub_tile: '66', + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [SUB_TILE_66, SUB_TILE_65], expect) + ); + }); + + it('should return 200 status code and no sub tiles as disable_fuzziness: true', async function () { + const requestParams: GetTilesQueryParams = { + tile: 'RIT', + sub_tile: '11', + limit: 5, + disable_fuzziness: true, + }; + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [], expect) + ); + }); + + it('should return 200 status code and sub_tile "66" filtered by geo_context (UTM)', async function () { + const geo_context = { x: 288240, y: 4645787, zone: 33, radius: 100 }; + const requestParams: GetTilesQueryParams = { + tile: 'RIT', + sub_tile: '66', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + }); + + // expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [SUB_TILE_66], expect) + ); + }); + + it('should return 200 status code and sub_tile "66" filtered by geo_context (WGS84)', async function () { + const geo_context = { lon: 12.445715777842793, lat: 41.935651320498835, radius: 100 }; + const requestParams: GetTilesQueryParams = { + tile: 'RIT', + sub_tile: '66', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + }); + + // expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [SUB_TILE_66], expect) + ); + }); + it('should return 200 status code and sub_tile "66" filtered by geo_context (bbox)', async function () { + const requestParams: GetTilesQueryParams = { + tile: 'RIT', + sub_tile: '66', + geo_context: { bbox: [12.442900073406776, 41.92988103633658, 12.450060653825801, 41.939618057332865] }, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [SUB_TILE_66], expect) + ); + }); + + it('should return 200 status code and control tiles by MGRS', async function () { + const requestParams: GetTilesQueryParams = { + mgrs: '33TTG958462', + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIT_TILE, RIC_TILE], expect) + ); + }); + + it('should return 200 status code and control tiles by MGRS and geo_context filter (WGS84)', async function () { + const geo_context: { bbox: BBox } = { bbox: [12.593772987581218, 41.89988905812697, 12.593772987581218, 41.89988905812697] }; + const requestParams: GetTilesQueryParams = { + mgrs: '33TTG958462', + geo_context, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE], expect) + ); + }); + + it('should return 200 status code and control tiles by MGRS and geo_context bias (WGS84)', async function () { + const geo_context: { bbox: BBox } = { bbox: [12.593772987581218, 41.89988905812697, 12.593772987581218, 41.89988905812697] }; + const requestParams: GetTilesQueryParams = { + mgrs: '33TTG958462', + geo_context, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: false, + }; + const response = await requestSender.getTiles({ + ...requestParams, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + }); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject>>( + expectedResponse(requestParams, [RIC_TILE, RIT_TILE], expect) + ); + }); + }); + describe('Bad Path', function () { + // All requests with status code of 400 + it('should return 400 status code and error message when tile or mgrs is not defined', async function () { + const response = await requestSender.getTiles({} as unknown as GetTilesQueryParams); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: "/control/tiles: only one of 'tile' or 'mgrs' query parameter must be defined", + }); + }); + + it('should return 400 status code and error message when tile value is empty', async function () { + const response = await requestSender.getTiles({ tile: '', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: "Empty value found for query parameter 'tile'", + }); + }); + + it('should return 400 status code and error message when tile value is invalid', async function () { + let response = await requestSender.getTiles({ tile: 'invalid', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: 'request/query/tile must NOT have more than 3 characters', + }); + + response = await requestSender.getTiles({ tile: 'i', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: 'request/query/tile must NOT have fewer than 2 characters', + }); + }); + + it('should return 400 status code and error message when mgrs value is invalid', async function () { + let response = await requestSender.getTiles({ mgrs: '', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: "Empty value found for query parameter 'mgrs'", + }); + + response = await requestSender.getTiles({ tile: 'i', limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: 'request/query/tile must NOT have fewer than 2 characters', + }); + }); + + test.each(['{dsa}', '0', 'z', '000', '!321'])( + 'should return 400 status code and error message when mgrs tile is invalid', + async (mgrs) => { + const response = await requestSender.getTiles({ mgrs, limit: 5, disable_fuzziness: false }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: `Invalid MGRS: ${mgrs}`, + }); + } + ); + + test.each([[[1]], [[1, 1]], [[1, 1, 1]], [[1, 1, 1, 1, 1]]])( + 'should return 400 status code and error message when bbox not containing 4 or 6 values', + async function (bbox) { + const response = await requestSender.getTiles({ + tile: 'RIT', + limit: 5, + disable_fuzziness: false, + geo_context: { bbox: bbox as BBox }, + geo_context_mode: GeoContextMode.FILTER, + }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: + 'geo_context validation: geo_context must contain one of the following: {"bbox": [number,number,number,number] | [number,number,number,number,number,number]}, {"lat": number, "lon": number, "radius": number}, or {"x": number, "y": number, "zone": number, "radius": number}', + }); + } + ); + + test.each(['invalid', '6a6', '06', '-11', '6 ', ' 6', ' ', ' 6 ', ''])( + 'should return 400 status code and error message when sub_tile value is invalid', + async (sub_tile) => { + const response = await requestSender.getTiles({ tile: 'RIT', sub_tile, limit: 5, disable_fuzziness: false }); + + const message = sub_tile ? 'request/query/sub_tile must match pattern "^[1-9][0-9]*$"' : "Empty value found for query parameter 'sub_tile'"; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + } + ); + + test.each>([ + { + geo_context: { x: 300850, y: 4642203, zone: 33, radius: 100 }, + }, + { + geo_context: { lon: 12.598899687444742, lat: 41.90667824634701, radius: 10 }, + }, + { + geo_context: { bbox: [12.554407132912445, 41.84962590648513, 12.652837919839953, 41.94545380230761] }, + }, + { + geo_context_mode: GeoContextMode.BIAS, + }, + { + geo_context_mode: GeoContextMode.FILTER, + }, + ])('should return 400 and message that geo_context and geo_context_mode must be both defined or both undefined', async function (requestParams) { + const response = await requestSender.getTiles({ tile: 'RIT', limit: 5, disable_fuzziness: false, ...requestParams }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: '/control/utils/geoContextQuery: geo_context and geo_context_mode must be both defined or both undefined', + }); + }); + + test.each>([ + { + disable_fuzziness: '' as unknown as boolean, + }, + { + disable_fuzziness: 'True' as unknown as boolean, + }, + { + disable_fuzziness: 'False' as unknown as boolean, + }, + ])('should return 400 status code and error message when disable_fuzziness value is invalid', async function (requestParams) { + const response = await requestSender.getTiles({ tile: 'RIT', limit: 5, ...requestParams }); + + const message = + (requestParams.disable_fuzziness as unknown as string) === '' + ? "Empty value found for query parameter 'disable_fuzziness'" + : 'request/query/disable_fuzziness must be boolean'; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + }); + + test.each>([ + { + limit: '' as unknown as number, + }, + { + limit: 0, + }, + { + limit: 101, + }, + ])('should return 400 status code and error message when limit value is invalid', async function (requestParams) { + const response = await requestSender.getTiles({ tile: 'RIT', disable_fuzziness: false, ...requestParams }); + + const message = + (requestParams.limit as unknown as string) === '' + ? "Empty value found for query parameter 'limit'" + : requestParams.limit < 1 + ? 'request/query/limit must be >= 1' + : 'request/query/limit must be <= 15'; + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message, + }); + }); + + test.each<(keyof GeoContext)[][]>([[['lat', 'lon', 'radius']], [['x', 'y', 'zone', 'radius']]])( + 'should return 400 for all invalid geo_context object for the keys %s', + async function (keys) { + function generateCombinations(keys: (keyof GeoContext)[]): GeoContext[] { + const combinations: object[] = []; + + function backtrack(current: object, remainingKeys: string[]): void { + if (remainingKeys.length === 0) { + combinations.push(current); + return; + } + + const key = remainingKeys[0]; + const remaining = remainingKeys.slice(1); + + backtrack({ ...current, [key]: 1 }, remaining); + backtrack(current, remaining); + } + + backtrack({}, keys); + + return combinations; + } + + const geoContexts = generateCombinations(keys); + + for (const geo_context of geoContexts) { + if (Object.keys(geo_context).length === keys.length) { + continue; + } + const response = await requestSender.getTiles({ + tile: 'RIT', + limit: 5, + disable_fuzziness: false, + geo_context: JSON.stringify(geo_context) as unknown as GetTilesQueryParams['geo_context'], + geo_context_mode: GeoContextMode.BIAS, + }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: + 'geo_context validation: geo_context must contain one of the following: {"bbox": [number,number,number,number] | [number,number,number,number,number,number]}, {"lat": number, "lon": number, "radius": number}, or {"x": number, "y": number, "zone": number, "radius": number}', + }); + } + } + ); + }); + describe('Sad Path', function () { + // All requests with status code 4XX-5XX + }); +}); diff --git a/tests/integration/control/utils.ts b/tests/integration/control/utils.ts new file mode 100644 index 00000000..889b2f9e --- /dev/null +++ b/tests/integration/control/utils.ts @@ -0,0 +1,50 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { CommonRequestParameters, GenericGeocodingResponse } from '../../../src/common/interfaces'; +import { GetItemsQueryParams } from '../../../src/control/item/controllers/itemController'; +import { Item } from '../../../src/control/item/models/item'; +import { GetRoutesQueryParams } from '../../../src/control/route/controllers/routeController'; +import { Route } from '../../../src/control/route/models/route'; +import { GetTilesQueryParams } from '../../../src/control/tile/controllers/tileController'; +import { Tile } from '../../../src/control/tile/models/tile'; + +const expectedObjectWithScore = (source: T, expect: jest.Expect): GenericGeocodingResponse['features'][number] => ({ + ...source, + properties: { + ...source.properties, + score: expect.any(Number) as number, + matches: [ + { + layer: expect.any(String) as string, + source: expect.any(String) as string, + source_id: [], + }, + ], + names: { + default: [expect.any(String) as string], + display: expect.any(String) as string, + }, + }, +}); + +const expectedGeocodingElasticResponseMetrics = ( + resultsCount: number, + expect: jest.Expect +): NonNullable['geocoding']>['response'] => ({ + results_count: resultsCount, + max_score: expect.any(Number) as number, + match_latency_ms: expect.any(Number) as number, +}); + +export const expectedResponse = ( + requestParams: U, + arr: T[], + expect: jest.Expect +): GenericGeocodingResponse> => ({ + type: 'FeatureCollection', + geocoding: { + version: process.env.npm_package_version, + query: requestParams, + response: expectedGeocodingElasticResponseMetrics(arr.length, expect), + }, + features: arr.map((item) => expectedObjectWithScore(item, expect)), +}); diff --git a/tests/integration/docs/docs.spec.ts b/tests/integration/docs/docs.spec.ts index 88f06840..e818ec69 100644 --- a/tests/integration/docs/docs.spec.ts +++ b/tests/integration/docs/docs.spec.ts @@ -3,6 +3,8 @@ import { trace } from '@opentelemetry/api'; import httpStatusCodes from 'http-status-codes'; import { getApp } from '../../../src/app'; import { SERVICES } from '../../../src/common/constants'; +import { S3_REPOSITORY_SYMBOL } from '../../../src/common/s3/s3Repository'; +import { cronLoadTileLatLonDataSymbol } from '../../../src/latLon/DAL/latLonDAL'; import { DocsRequestSender } from './helpers/docsRequestSender'; describe('docs', function () { @@ -12,6 +14,9 @@ describe('docs', function () { override: [ { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { token: S3_REPOSITORY_SYMBOL, provider: { useValue: {} } }, + { token: SERVICES.S3_CLIENT, provider: { useValue: {} } }, + { token: cronLoadTileLatLonDataSymbol, provider: { useValue: {} } }, ], useChild: true, }); diff --git a/tests/integration/geotextSearch/geotext.spec.ts b/tests/integration/geotextSearch/geotext.spec.ts deleted file mode 100644 index 42f0c526..00000000 --- a/tests/integration/geotextSearch/geotext.spec.ts +++ /dev/null @@ -1,172 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import config from 'config'; -import jsLogger from '@map-colonies/js-logger'; -import { trace } from '@opentelemetry/api'; -import httpStatusCodes from 'http-status-codes'; -import nock from 'nock'; -import { getApp } from '../../../src/app'; -import { SERVICES } from '../../../src/common/constants'; -import { IApplication } from '../../../src/common/interfaces'; -import { jfkAirport, losAngelesAirport, policeAirport } from './possibleObjects'; -import { GeotextSearchRequestSender } from './helpers/requestSender'; - -describe('/query', function () { - let requestSender: GeotextSearchRequestSender; - - beforeEach(async function () { - const app = await getApp({ - override: [ - { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, - { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, - ], - useChild: true, - }); - requestSender = new GeotextSearchRequestSender(app.app); - }); - - describe('Happy Path', function () { - it('should return 200 status code and the all available regions', async function () { - const response = await requestSender.getRegions(); - - expect(response.status).toBe(httpStatusCodes.OK); - // expect(response).toSatisfyApiSpec(); - - expect(response.body).toEqual(['USA']); - }); - - it('should return 200 status code and all airports', async function () { - const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) - .post('', { tokens: ['airport'] }) - .reply(httpStatusCodes.OK, [ - { - tokens: ['airport'], - prediction: ['essence'], - }, - ]); - - const response = await requestSender.getGeotextSearch({ query: 'airport', limit: 10 }); - - expect(response.status).toBe(httpStatusCodes.OK); - - // expect(response).toSatisfyApiSpec(); - - expect(response.body).toMatchObject({ - type: 'FeatureCollection', - geocoding: { - version: process.env.npm_package_version, - query: { - query: 'airport', - limit: 10, - name: '', - placeTypes: ['transportation'], - subPlaceTypes: ['airport'], - hierarchies: [], - }, - name: '', - }, - features: [jfkAirport(1), policeAirport(2), losAngelesAirport(3)], - }); - - tokenTypesUrlScope.done(); - }); - }); - - it('should return 200 and los angeles airport while checking the hierarchy system', async () => { - const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) - .post('', { tokens: ['airport'] }) - .reply(httpStatusCodes.OK, [ - { - tokens: ['airport'], - prediction: ['essence'], - }, - ]); - - const response = await requestSender.getGeotextSearch({ query: 'airport, los angeles', limit: 1 }); - - expect(response.status).toBe(httpStatusCodes.OK); - // expect(response).toSatisfyApiSpec(); - expect(response.body).toMatchObject({ - type: 'FeatureCollection', - geocoding: { - version: process.env.npm_package_version, - query: { - query: 'airport', - limit: 1, - name: '', - placeTypes: ['transportation'], - subPlaceTypes: ['airport'], - hierarchies: [ - { - geo_json: { - coordinates: [ - [ - [-118.54430957638033, 34.07939240620722], - [-118.5350828996408, 33.695192367610986], - [-118.04596133238863, 33.47690745532634], - [-117.66265886139905, 33.379872950239346], - [-117.57145361502153, 33.63336904289318], - [-117.67279277766329, 34.23871934668085], - [-118.2605599209839, 34.28059749364003], - [-118.54430957638033, 34.07939240620722], - ], - ], - type: 'Polygon', - }, - hierarchy: 'city', - placetype: 'city', - region: 'USA', - text: 'Los Angeles', - weight: 1.1, - }, - ], - }, - name: '', - }, - features: [losAngelesAirport(1)], - }); - - tokenTypesUrlScope.done(); - }); - - it('should retrun 200 and los angeles airport while checking the region from NLP analyser', async () => { - const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) - .post('', { tokens: ['airport', 'los', 'angeles'] }) - .reply(httpStatusCodes.OK, [ - { - tokens: ['airport', 'los', 'angeles'], - prediction: ['essence', 'essence', 'name'], - }, - ]); - - const response = await requestSender.getGeotextSearch({ query: 'airport los angeles', limit: 1 }); - - expect(response.status).toBe(httpStatusCodes.OK); - - // expect(response).toSatisfyApiSpec(); - - expect(response.body).toMatchObject({ - type: 'FeatureCollection', - geocoding: { - version: process.env.npm_package_version, - query: { - query: 'airport los angeles', - limit: 1, - name: 'angeles', - placeTypes: ['transportation'], - subPlaceTypes: ['airport'], - hierarchies: [], - }, - name: 'angeles', - }, - features: [losAngelesAirport(1)], - }); - - tokenTypesUrlScope.done(); - }); - describe('Bad Path', function () { - // All requests with status code of 400 - }); - describe('Sad Path', function () { - // All requests with status code 4XX-5XX - }); -}); diff --git a/tests/integration/geotextSearch/helpers/requestSender.ts b/tests/integration/geotextSearch/helpers/requestSender.ts deleted file mode 100644 index c924f538..00000000 --- a/tests/integration/geotextSearch/helpers/requestSender.ts +++ /dev/null @@ -1,25 +0,0 @@ -import * as supertest from 'supertest'; -import { GetGeotextSearchParams } from '../../../../src/geotextSearch/interfaces'; - -export class GeotextSearchRequestSender { - public constructor(private readonly app: Express.Application) {} - - public async getGeotextSearch(queryParams?: GetGeotextSearchParams): Promise { - return supertest - .agent(this.app) - .get('/v1/query') - .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') - .set('x-user-id', 'abc123') - .query(queryParams ?? {}); - } - - public async getRegions(): Promise { - return supertest - .agent(this.app) - .get('/v1/query/regions') - .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') - .set('x-user-id', 'abc123'); - } -} diff --git a/tests/integration/geotextSearch/possibleObjects.ts b/tests/integration/geotextSearch/possibleObjects.ts deleted file mode 100644 index 5ddd36e2..00000000 --- a/tests/integration/geotextSearch/possibleObjects.ts +++ /dev/null @@ -1,167 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -/* eslint-disable @typescript-eslint/no-magic-numbers */ -export const jfkAirport = (rank: number) => ({ - type: 'Feature', - geometry: { - type: 'Polygon', - coordinates: [ - [ - [-73.81278266814672, 40.66039916690434], - [-73.8177430790069, 40.66160065836647], - [-73.82178645734075, 40.66043068890016], - [-73.82341214553732, 40.658185554886586], - [-73.82407909454082, 40.65496001884071], - [-73.8229536180974, 40.650532558586235], - [-73.82211993184252, 40.647939195437345], - [-73.81290769732489, 40.643985699842915], - [-73.79214887014153, 40.63414837731818], - [-73.78339516446982, 40.62987771430167], - [-73.7898562329419, 40.62275933562921], - [-73.78443726476769, 40.620069953803636], - [-73.7791433570518, 40.627188619100366], - [-73.77639219241223, 40.62706207167477], - [-73.77159849644941, 40.62336045339214], - [-73.77209870820208, 40.619975033140975], - [-73.77047302000595, 40.61908910045176], - [-73.76547094984971, 40.628422477310664], - [-73.75338249916041, 40.63291467256053], - [-73.74733827381596, 40.63601474373485], - [-73.7467963777506, 40.64208793530722], - [-73.752548854642, 40.64749646458006], - [-73.76213624656812, 40.65309424493557], - [-73.78181122379466, 40.66270746643491], - [-73.79106514121902, 40.66438330508498], - [-73.7957754685564, 40.665205399216404], - [-73.79856831750864, 40.66283394629252], - [-73.80390390953731, 40.66175885985783], - [-73.8073637074931, 40.66039916690434], - [-73.8109068740743, 40.66074699797244], - [-73.81278266814672, 40.66039916690434], - ], - ], - }, - properties: { - rank, - source: 'OSM', - layer: 'osm_airports', - source_id: ['03ed6d97-fc81-4340-b68a-11993554eef1'], - name: { - en: ['JFK Airport'], - fr: ['Aeropuerto JFK'], - default: ['JFK'], - display: 'JFK', - }, - placetype: 'transportation', - sub_placetype: 'airport', - regions: [ - { - region: 'USA', - sub_regions: ['New York'], - }, - ], - region: ['USA'], - sub_region: ['New York'], - }, -}); - -export const policeAirport = (rank: number) => ({ - type: 'Feature', - geometry: { - coordinates: [ - [ - [-73.50019138870562, 40.76503398530525], - [-73.49991804403106, 40.75762191351754], - [-73.50172211928987, 40.75368779651572], - [-73.502924836068, 40.74958778448408], - [-73.50095675396722, 40.746067370663354], - [-73.49587254187276, 40.74117989901089], - [-73.48810955136324, 40.74010295019133], - [-73.4862508071566, 40.74097279482331], - [-73.48450140084462, 40.74209114977816], - [-73.48335335295225, 40.743250905424986], - [-73.49368578398266, 40.7500847690697], - [-73.49363111503533, 40.75112014169309], - [-73.49166303293454, 40.75087165373341], - [-73.49128035030373, 40.75435040065955], - [-73.48406404926651, 40.75385344795657], - [-73.4860321313673, 40.75840870869581], - [-73.50019138870562, 40.76503398530525], - ], - ], - type: 'Polygon', - }, - properties: { - rank, - source: 'OSM', - layer: 'osm_airports', - source_id: ['009c6b65-3dcb-4c4f-9f02-d766ebb5d808'], - name: { - en: ['Nassau County Police Airport'], - fr: ['Aeropuerto de la Policía del Condado de Nassau'], - default: ['Nassau County Police Airport'], - display: 'Nassau County Police Airport', - }, - placetype: 'transportation', - sub_placetype: 'airport', - regions: [ - { - region: 'USA', - sub_regions: ['New York'], - }, - ], - region: ['USA'], - sub_region: ['New York'], - }, -}); - -export const losAngelesAirport = (rank: number) => ({ - type: 'Feature', - geometry: { - coordinates: [ - [ - [-118.42713070992883, 33.9512236894319], - [-118.43440343023548, 33.94992163234602], - [-118.43571147345608, 33.94670980636225], - [-118.43560682999878, 33.943107208682775], - [-118.43325235220159, 33.938723119106925], - [-118.42875268352236, 33.9310829780122], - [-118.41394563426408, 33.931820976094315], - [-118.39756893251248, 33.932255089782316], - [-118.38516868278077, 33.935250412835686], - [-118.37904703905322, 33.936509285268926], - [-118.37899471732432, 33.943367642688045], - [-118.3788377519829, 33.94501703885841], - [-118.39181354073204, 33.94527746687625], - [-118.39510980964849, 33.945451085112225], - [-118.3980398264626, 33.94558129855595], - [-118.39746428744567, 33.949748023597365], - [-118.39720267894361, 33.953003135913036], - [-118.40813791735772, 33.95252573110099], - [-118.42713070992883, 33.9512236894319], - ], - ], - type: 'Polygon', - }, - properties: { - rank, - source: 'OSM', - layer: 'osm_airports', - source_id: ['a4f373ab-b824-41e2-b160-e7729c73bea6'], - name: { - en: ['Los Angeles International Airport'], - fr: ['Aeropuerto Internacional de Los Ángeles'], - default: ['Los Angeles International Airport'], - display: 'Los Angeles International Airport', - }, - placetype: 'transportation', - sub_placetype: 'airport', - regions: [ - { - region: 'USA', - sub_regions: ['Los Angeles'], - }, - ], - region: ['USA'], - sub_region: ['Los Angeles'], - }, -}); diff --git a/tests/integration/globalSetup.ts b/tests/integration/globalSetup.ts new file mode 100644 index 00000000..ee34e0d7 --- /dev/null +++ b/tests/integration/globalSetup.ts @@ -0,0 +1,39 @@ +import 'reflect-metadata'; +import jsLogger from '@map-colonies/js-logger'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; +import { trace } from '@opentelemetry/api'; +import { getApp } from '../../src/app'; +import { SERVICES } from '../../src/common/constants'; +import { IConfig } from '../../src/common/interfaces'; +import importDataToS3 from '../../devScripts/importDataToS3'; +import importDataToElastic from '../../devScripts/importDataToElastic'; +import { cronLoadTileLatLonDataSymbol } from '../../src/latLon/DAL/latLonDAL'; + +export default async () => { + const app = await getApp({ + override: [ + { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, + { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { + token: cronLoadTileLatLonDataSymbol, + provider: { useValue: {} }, + }, + ], + useChild: true, + }); + + const config = app.container.resolve(SERVICES.CONFIG); + + await Promise.allSettled([await importDataToS3(config), await importDataToElastic(config)]).then((results) => { + results.forEach((result) => { + if (result.status === 'rejected') { + throw result.reason; + } + }); + }); + + const cleanupRegistry = app.container.resolve(SERVICES.CLEANUP_REGISTRY); + await cleanupRegistry.trigger(); + app.container.reset(); + return; +}; diff --git a/tests/integration/globalTeardown.ts b/tests/integration/globalTeardown.ts new file mode 100644 index 00000000..96003dc9 --- /dev/null +++ b/tests/integration/globalTeardown.ts @@ -0,0 +1,84 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import 'reflect-metadata'; +import jsLogger from '@map-colonies/js-logger'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; +import { trace } from '@opentelemetry/api'; +import { DeleteBucketCommand, DeleteObjectCommand, S3Client } from '@aws-sdk/client-s3'; +import { getApp } from '../../src/app'; +import { elasticConfigPath, SERVICES } from '../../src/common/constants'; +import { IConfig } from '../../src/common/interfaces'; +import { S3Config, s3ConfigPath } from '../../src/common/s3'; +import { ElasticDbClientsConfig } from '../../src/common/elastic/interfaces'; +import { ElasticClients } from '../../src/common/elastic'; +import { cronLoadTileLatLonDataSymbol } from '../../src/latLon/DAL/latLonDAL'; + +export default async (): Promise => { + const app = await getApp({ + override: [ + { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, + { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { + token: cronLoadTileLatLonDataSymbol, + provider: { useValue: {} }, + }, + ], + useChild: true, + }); + + const config = app.container.resolve(SERVICES.CONFIG); + + const s3Client = app.container.resolve(SERVICES.S3_CLIENT); + const elasticClients = app.container.resolve(SERVICES.ELASTIC_CLIENTS); + + const s3Config = config.get(s3ConfigPath); + const elasticClientsConfig = config.get(elasticConfigPath); + + const clearS3Data = new Promise((resolve, reject) => { + void (async (): Promise => { + try { + if (s3Config.files.latLonConvertionTable !== undefined) { + await s3Client.send( + new DeleteObjectCommand({ Bucket: s3Config.files.latLonConvertionTable.bucket, Key: s3Config.files.latLonConvertionTable.fileName }) + ); + await s3Client.send(new DeleteBucketCommand({ Bucket: s3Config.files.latLonConvertionTable.bucket })); + } + resolve(); + } catch (error) { + console.error(error); + reject(error); + } + })(); + }); + + const clearElasticData = new Promise((resolve, reject) => { + void (async (): Promise => { + try { + for (const [key, value] of Object.entries(elasticClientsConfig)) { + await elasticClients[key as keyof ElasticDbClientsConfig].indices.delete({ + index: typeof value.properties.index === 'string' ? value.properties.index : Object.values(value.properties.index), + }); + } + resolve(); + } catch (error) { + console.error(error); + reject(error); + } + })(); + }); + + await Promise.allSettled([clearS3Data, clearElasticData]).then((results) => { + results.forEach((result) => { + if (result.status === 'rejected') { + throw result.reason; + } + }); + }); + + const cleanupRegistry = app.container.resolve(SERVICES.CLEANUP_REGISTRY); + await cleanupRegistry.trigger(); + app.container.reset(); + await app.container.dispose(); + + console.log('Global Teardown completed'); + return; +}; diff --git a/tests/integration/item/item.spec.ts b/tests/integration/item/item.spec.ts deleted file mode 100644 index d1eb396b..00000000 --- a/tests/integration/item/item.spec.ts +++ /dev/null @@ -1,67 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import jsLogger from '@map-colonies/js-logger'; -import { trace } from '@opentelemetry/api'; -import httpStatusCodes from 'http-status-codes'; -import { getApp } from '../../../src/app'; -import { SERVICES } from '../../../src/common/constants'; -import { GetItemsQueryParams } from '../../../src/item/controllers/itemController'; -import { ItemRequestSender } from './helpers/requestSender'; - -describe('/items', function () { - let requestSender: ItemRequestSender; - - beforeEach(async function () { - const app = await getApp({ - override: [ - { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, - { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, - ], - useChild: true, - }); - requestSender = new ItemRequestSender(app.app); - }); - - describe('Happy Path', function () { - it('should return 200 status code and the item', async function () { - const response = await requestSender.getItems({ command_name: '4805' }); - - expect(response.status).toBe(httpStatusCodes.OK); - expect(response).toSatisfyApiSpec(); - }); - it('should return 200 status code and empty response', async function () { - const response = await requestSender.getItems({ command_name: '48054805' }); - - expect(response.status).toBe(httpStatusCodes.OK); - expect(response).toSatisfyApiSpec(); - expect(response.body).toMatchObject({ - type: 'FeatureCollection', - features: [], - }); - }); - }); - describe('Bad Path', function () { - // All requests with status code of 400 - it('Should return 400 status code and meessage "request/query must have required property \'command_name\'"', async function () { - const message = "request/query must have required property 'command_name'"; - - const response = await requestSender.getItems(); - - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ message }); - expect(response).toSatisfyApiSpec(); - }); - it('Should return 400 status code for unknown parameter', async function () { - const parameter = 'test1234'; - const message = `Unknown query parameter '${parameter}'`; - - const response = await requestSender.getItems({ [parameter]: parameter } as unknown as GetItemsQueryParams); - - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ message }); - expect(response).toSatisfyApiSpec(); - }); - }); - describe('Sad Path', function () { - // All requests with status code 4XX-5XX - }); -}); diff --git a/tests/integration/latLon/helpers/requestSender.ts b/tests/integration/latLon/helpers/requestSender.ts index 8ace3344..2766e47c 100644 --- a/tests/integration/latLon/helpers/requestSender.ts +++ b/tests/integration/latLon/helpers/requestSender.ts @@ -1,52 +1,15 @@ import * as supertest from 'supertest'; -import { - GetLatLonToTileQueryParams, - GetTileToLatLonQueryParams, - GetLatLonToMgrsQueryParams, - GetMgrsToLatLonQueryParams, -} from '../../../../src/latLon/controllers/latLonController'; - -const PREFIX = '/v1/lookup'; +import { GetCoordinatesRequestParams } from '../../../../src/latLon/controllers/latLonController'; export class LatLonRequestSender { public constructor(private readonly app: Express.Application) {} - public async getLatlonToTile(queryParams?: GetLatLonToTileQueryParams): Promise { - return supertest - .agent(this.app) - .get(`${PREFIX}/latlonToTile`) - .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') - .set('x-user-id', 'abc123') - .query(queryParams ?? {}); - } - - public async getTileToLatLon(queryParams?: GetTileToLatLonQueryParams): Promise { - return supertest - .agent(this.app) - .get(`${PREFIX}/tileToLatLon`) - .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') - .set('x-user-id', 'abc123') - .query(queryParams ?? {}); - } - - public async getLatlonToMgrs(queryParams?: GetLatLonToMgrsQueryParams): Promise { - return supertest - .agent(this.app) - .get(`${PREFIX}/latlonToMgrs`) - .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') - .set('x-user-id', 'abc123') - .query(queryParams ?? {}); - } - - public async getMgrsToLatlon(queryParams?: GetMgrsToLatLonQueryParams): Promise { + public async convertCoordinatesToGrid(queryParams?: GetCoordinatesRequestParams): Promise { return supertest .agent(this.app) - .get(`${PREFIX}/mgrsToLatLon`) + .get(`/lookup/coordinates`) .set('Content-Type', 'application/json') - .set('X-API-Key', 'abc123') + .set('x-api-key', 'abc123') .set('x-user-id', 'abc123') .query(queryParams ?? {}); } diff --git a/tests/integration/latLon/latLon.spec.ts b/tests/integration/latLon/latLon.spec.ts index c656effb..17e53bbe 100644 --- a/tests/integration/latLon/latLon.spec.ts +++ b/tests/integration/latLon/latLon.spec.ts @@ -1,169 +1,209 @@ -/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/naming-convention */ import jsLogger from '@map-colonies/js-logger'; import { trace } from '@opentelemetry/api'; +import { Application } from 'express'; +import { DependencyContainer } from 'tsyringe'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; import httpStatusCodes from 'http-status-codes'; import { getApp } from '../../../src/app'; import { SERVICES } from '../../../src/common/constants'; +import { LatLonDAL } from '../../../src/latLon/DAL/latLonDAL'; import { LatLonRequestSender } from './helpers/requestSender'; -describe('/latLon', function () { +describe('/lookup', function () { let requestSender: LatLonRequestSender; + let app: { app: Application; container: DependencyContainer }; beforeEach(async function () { - const app = await getApp({ + app = await getApp({ override: [ { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, ], useChild: true, }); - requestSender = new LatLonRequestSender(app.app); - }, 20000); - - describe('Happy Path', function () { - it('should return 200 status code and lat-lon from mgrs', async function () { - const response = await requestSender.getMgrsToLatlon({ mgrs: '18TWL8565011369' }); - expect(response.status).toBe(httpStatusCodes.OK); - expect(response).toSatisfyApiSpec(); - expect(response.body).toMatchObject({ - lat: 40.74882151233783, - lon: -73.98543192220956, - }); - }); + requestSender = new LatLonRequestSender(app.app); + }); - it('should return 200 status code and mgrs from lat-lon', async function () { - const reponse = await requestSender.getLatlonToMgrs({ - lat: 40.74882151233783, - lon: -73.98543192220956, - }); + afterAll(async function () { + const cleanupRegistry = app.container.resolve(SERVICES.CLEANUP_REGISTRY); + await cleanupRegistry.trigger(); + app.container.reset(); - expect(reponse.status).toBe(httpStatusCodes.OK); - expect(reponse).toSatisfyApiSpec(); - expect(reponse.body).toMatchObject({ - mgrs: '18TWL8565011369', - }); - }); + jest.clearAllTimers(); + }); + describe('Happy Path', function () { it('should return 200 status code and tile from lat-lon', async function () { - const reponse = await requestSender.getLatlonToTile({ + const response = await requestSender.convertCoordinatesToGrid({ lat: 52.57326537485767, lon: 12.948781146422107, + target_grid: 'control', }); - expect(reponse.status).toBe(httpStatusCodes.OK); - expect(reponse).toSatisfyApiSpec(); - expect(reponse.body).toMatchObject({ - tileName: 'BRN', - subTileNumber: [0, 0, 0], + expect(response.status).toBe(httpStatusCodes.OK); + expect(response).toSatisfyApiSpec(); + expect(response.body).toEqual({ + type: 'Feature', + geocoding: { + version: expect.any(String) as string, + query: { + lat: 52.57326537485767, + lon: 12.948781146422107, + target_grid: 'control', + }, + response: { + max_score: 1, + results_count: 1, + match_latency_ms: 0, + }, + }, + bbox: [12.93694771534361, 52.51211561266182, 13.080296161196031, 52.60444267653175], + geometry: { + type: 'Polygon', + coordinates: [ + [ + [12.93694771534361, 52.51211561266182], + [12.93694771534361, 52.60444267653175], + [13.080296161196031, 52.60444267653175], + [13.080296161196031, 52.51211561266182], + [12.93694771534361, 52.51211561266182], + ], + ], + }, + properties: { + name: 'BRN', + tileName: 'BRN', + subTileNumber: ['06', '97', '97'], + }, }); }); - it('should return 200 status code and lat-lon from tile', async function () { - const reponse = await requestSender.getTileToLatLon({ - tile: 'BRN', - sub_tile_number: [10, 10, 10], + it('should return 200 status code and MGRS from lat-lon', async function () { + const response = await requestSender.convertCoordinatesToGrid({ + lat: 52.57326537485767, + lon: 12.948781146422107, + target_grid: 'MGRS', }); - expect(reponse.status).toBe(httpStatusCodes.OK); - expect(reponse).toSatisfyApiSpec(); - expect(reponse.body).toMatchObject({ - type: 'FeatureCollection', - features: [ - { - geometry: { - coordinates: [ - [ - [12.953293384350397, 52.512399536846765], - [12.953440643865289, 52.512402084451686], - [12.953436468347887, 52.51249192878939], - [12.95328920853307, 52.512489381176245], - [12.953293384350397, 52.512399536846765], - ], - ], - type: 'Polygon', - }, - properties: { - TYPE: 'TILE', - SUB_TILE_NUMBER: [10, 10, 10], - TILE_NAME: 'BRN', - }, + expect(response.status).toBe(httpStatusCodes.OK); + expect(response).toSatisfyApiSpec(); + expect(response.body).toEqual({ + type: 'Feature', + geocoding: { + version: expect.any(String) as string, + query: { + lat: 52.57326537485767, + lon: 12.948781146422107, + target_grid: 'MGRS', }, - ], + response: { + max_score: 1, + results_count: 1, + match_latency_ms: 0, + }, + }, + bbox: [12.948777289238832, 52.57325754975297, 12.948791616108007, 52.57326678960368], + geometry: { + type: 'Point', + coordinates: [12.948781146422107, 52.57326537485767], + }, + properties: { + name: '33UUU6099626777', + accuracy: '1m', + mgrs: '33UUU6099626777', + }, }); }); }); + describe('Bad Path', function () { - test.each<[keyof typeof requestSender, string[]]>([ - ['getLatlonToMgrs', ['lat', 'lon']], - ['getMgrsToLatlon', ['mgrs']], - ['getLatlonToTile', ['lat', 'lon']], - ['getTileToLatLon', ['tile', 'sub_tile_number']], - ])('should return 400 and message for missing required parameters', async (request, missingProperties) => { - const message = 'request/query must have required property'; - const response = await requestSender[request](); + it('should retrun 400 status code when invalid lat lon', async function () { + const response = await requestSender.convertCoordinatesToGrid({ + lat: 67.9435100890131, + lon: -84.41903041752825, + target_grid: 'control', + }); expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ message: missingProperties.map((txt) => `${message} '${txt}'`).join(', ') }); - expect(response).toSatisfyApiSpec(); - }); - - test.each<[keyof typeof requestSender]>([['getLatlonToMgrs'], ['getMgrsToLatlon'], ['getLatlonToTile'], ['getTileToLatLon']])( - 'should return 400 and message unknown query parameter', - async (request) => { - const parameter = 'test1234'; - const message = `Unknown query parameter '${parameter}'`; - - const response = await requestSender[request]({ [parameter]: parameter } as never); - - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ message }); - expect(response).toSatisfyApiSpec(); - } - ); - - it('should return 400 and check that all numbers are positive', async function () { - const message = "Invalid tile, check that 'tileName' and 'subTileNumber' exists and subTileNumber is array of size 3 with positive integers"; - - for (let i = 0; i < 3; i++) { - const arr: number[] = [10, 10, 10]; - arr[i] = 0; - - const response = await requestSender.getTileToLatLon({ - tile: 'BRN', - sub_tile_number: arr, - }); - - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ - message, - }); - expect(response).toSatisfyApiSpec(); - } + // expect(response).toSatisfyApiSpec(); + expect(response.body).toEqual({ + message: "Invalid lat lon, check 'lat' and 'lon' keys exists and their values are legal", + }); }); - it('should return 400 for lat-lon that outside the grid extent', async function () { - const response = await requestSender.getLatlonToTile({ lat: 1, lon: 1 }); + it('should return 400 status code when the coordinate is outside the grid extent', async function () { + const response = await requestSender.convertCoordinatesToGrid({ + lat: 32.57326537485767, + lon: 12.948781146422107, + target_grid: 'control', + }); expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ + // expect(response).toSatisfyApiSpec(); + expect(response.body).toEqual({ message: 'The coordinate is outside the grid extent', }); - expect(response).toSatisfyApiSpec(); }); + }); - it('should return 400 for tile not found', async function () { - const response = await requestSender.getTileToLatLon({ tile: 'XXX', sub_tile_number: [10, 10, 10] }); + describe('Sad Path', function () { + it('should return 500 as isDataLoadError is true for control', async function () { + const dataLoadErrorSpy = jest.spyOn(LatLonDAL.prototype, 'getIsDataLoadError').mockReturnValue(true); - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ - message: 'Tile not found', + const response = await requestSender.convertCoordinatesToGrid({ + lat: 32.57326537485767, + lon: 12.948781146422107, + target_grid: 'control', }); - expect(response).toSatisfyApiSpec(); + + expect(response.status).toBe(httpStatusCodes.INTERNAL_SERVER_ERROR); + // expect(response).toSatisfyApiSpec(); + expect(response.body).toEqual({ + message: 'Lat-lon to tile data currently not available', + stacktrace: expect.any(String) as string, + }); + + dataLoadErrorSpy.mockRestore(); }); + + // it('should return 500 as init is errored', async function () { + // const dataLoadErrorSpy = jest.spyOn(LatLonDAL.prototype as any, 'loadLatLonData').mockRejectedValue(new Error('some error')); + + // const response = await requestSender.convertCoordinatesToGrid({ + // lat: 52.57326537485767, + // lon: 12.948781146422107, + // target_grid: 'control', + // }); + + // console.log(response.status, response.body); + // expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + // // expect(response).toSatisfyApiSpec(); + // expect(response.body).toEqual({ + // message: 'Lat-lon to tile data currently not available', + // stacktrace: expect.any(String) as string, + // }); + + // dataLoadErrorSpy.mockRestore(); + // }); }); - describe('Sad Path', function () { - // All requests with status code 4XX-5XX + + it('should return error when cronPattern is not defined', async () => { + try { + await getApp({ + override: [ + { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, + { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { token: SERVICES.APPLICATION, provider: { useValue: {} } }, + ], + useChild: true, + }); + // If no error is thrown, fail the test + throw new Error('Expected error was not thrown'); + } catch (error) { + // eslint-disable-next-line jest/no-conditional-expect + expect((error as Error).message).toBe('cron pattern is not defined'); + } }); }); diff --git a/tests/integration/location/helpers/requestSender.ts b/tests/integration/location/helpers/requestSender.ts new file mode 100644 index 00000000..56b867f9 --- /dev/null +++ b/tests/integration/location/helpers/requestSender.ts @@ -0,0 +1,36 @@ +import * as supertest from 'supertest'; +import { GetGeotextSearchParams } from '../../../../src/location/interfaces'; + +export class LocationRequestSender { + private readonly pathPrefix = '/search/location'; + + public constructor(private readonly app: Express.Application) {} + + public async getQuery(queryParams?: GetGeotextSearchParams): Promise { + return supertest + .agent(this.app) + .get(`${this.pathPrefix}/query`) + .set('Content-Type', 'application/json') + .set('x-api-key', 'abc123') + .set('x-user-id', 'abc123') + .query(queryParams ?? {}); + } + + public async getRegions(): Promise { + return supertest + .agent(this.app) + .get(`${this.pathPrefix}/regions`) + .set('Content-Type', 'application/json') + .set('x-api-key', 'abc123') + .set('x-user-id', 'abc123'); + } + + public async getSources(): Promise { + return supertest + .agent(this.app) + .get(`${this.pathPrefix}/sources`) + .set('Content-Type', 'application/json') + .set('x-api-key', 'abc123') + .set('x-user-id', 'abc123'); + } +} diff --git a/tests/integration/location/location.spec.ts b/tests/integration/location/location.spec.ts new file mode 100644 index 00000000..46eb28a2 --- /dev/null +++ b/tests/integration/location/location.spec.ts @@ -0,0 +1,582 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import config from 'config'; +import { DependencyContainer } from 'tsyringe'; +import { Application } from 'express'; +import { Feature } from 'geojson'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; +import jsLogger from '@map-colonies/js-logger'; +import { trace } from '@opentelemetry/api'; +import httpStatusCodes from 'http-status-codes'; +import nock, { Body } from 'nock'; +import { getApp } from '../../../src/app'; +import { SERVICES } from '../../../src/common/constants'; +import { S3_REPOSITORY_SYMBOL } from '../../../src/common/s3/s3Repository'; +import { cronLoadTileLatLonDataSymbol } from '../../../src/latLon/DAL/latLonDAL'; +import { GetGeotextSearchParams } from '../../../src/location/interfaces'; +import { GenericGeocodingResponse, GeoContext, GeoContextMode, IApplication } from '../../../src/common/interfaces'; +import { LocationRequestSender } from './helpers/requestSender'; +import { + OSM_LA_PORT, + GOOGLE_LA_PORT, + LA_AIRPORT, + NY_JFK_AIRPORT, + NY_POLICE_AIRPORT, + NY_HIERRARCHY, + LA_HIERRARCHY, + MockLocationQueryFeature, + PARIS_WI_SCHOOL, +} from './mockObjects'; +import { expectedResponse, hierarchiesWithAnyWieght } from './utils'; + +describe('/search/location', function () { + let requestSender: LocationRequestSender; + let app: { app: Application; container: DependencyContainer }; + + beforeEach(async function () { + app = await getApp({ + override: [ + { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, + { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { token: S3_REPOSITORY_SYMBOL, provider: { useValue: {} } }, + { token: SERVICES.S3_CLIENT, provider: { useValue: {} } }, + { token: cronLoadTileLatLonDataSymbol, provider: { useValue: {} } }, + ], + useChild: true, + }); + + requestSender = new LocationRequestSender(app.app); + }); + + afterAll(async function () { + const cleanupRegistry = app.container.resolve(SERVICES.CLEANUP_REGISTRY); + await cleanupRegistry.trigger(); + nock.cleanAll(); + app.container.reset(); + + jest.clearAllTimers(); + }); + + describe('Happy Path', function () { + it('should return 200 status code and airports', async function () { + const requestParams: GetGeotextSearchParams = { query: 'airport', limit: 5, disable_fuzziness: true }; + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: requestParams.query.split(' ') }) + .reply(httpStatusCodes.OK, [ + { + tokens: ['airport'], + prediction: ['essence'], + }, + ]); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + + expect(response.body).toMatchObject>( + expectedResponse( + { + ...requestParams, + }, + { + place_types: ['transportation'], + sub_place_types: ['airport'], + hierarchies: [], + }, + [ + { + ...NY_JFK_AIRPORT, + properties: { + ...NY_JFK_AIRPORT.properties, + names: { + ...NY_JFK_AIRPORT.properties.names, + display: expect.stringContaining('JFK') as string, + }, + }, + }, + NY_POLICE_AIRPORT, + LA_AIRPORT, + ], + expect + ) + ); + + tokenTypesUrlScope.done(); + }); + + it('should return 200 status code and airports filtered by geo_context (bbox)', async function () { + const requestParams: GetGeotextSearchParams = { + query: 'airport', + geo_context: { bbox: [-75.81665, 39.597223, -72.575684, 41.352072] }, + geo_context_mode: GeoContextMode.FILTER, + limit: 5, + disable_fuzziness: true, + }; + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: requestParams.query.split(' ') }) + .reply(httpStatusCodes.OK, [ + { + tokens: ['airport'], + prediction: ['essence'], + }, + ]); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + + expect(response.body).toMatchObject>( + expectedResponse( + requestParams, + { + place_types: ['transportation'], + sub_place_types: ['airport'], + hierarchies: [], + }, + [ + { + ...NY_JFK_AIRPORT, + properties: { + ...NY_JFK_AIRPORT.properties, + names: { + ...NY_JFK_AIRPORT.properties.names, + display: expect.stringContaining('JFK') as string, + }, + }, + }, + NY_POLICE_AIRPORT, + ], + expect + ) + ); + + tokenTypesUrlScope.done(); + }); + + it('should return 200 status code and airports biased by geo_context (bbox)', async function () { + const requestParams: GetGeotextSearchParams = { + query: 'airport', + geo_context: { bbox: [-75.81665, 39.597223, -72.575684, 41.352072] }, + geo_context_mode: GeoContextMode.BIAS, + limit: 5, + disable_fuzziness: true, + }; + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: requestParams.query.split(' ') }) + .reply(httpStatusCodes.OK, [ + { + tokens: ['airport'], + prediction: ['essence'], + }, + ]); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + + expect(response.body).toMatchObject>( + expectedResponse( + { + ...requestParams, + }, + { + place_types: ['transportation'], + sub_place_types: ['airport'], + hierarchies: [], + }, + [ + { + ...NY_JFK_AIRPORT, + properties: { + ...NY_JFK_AIRPORT.properties, + names: { + ...NY_JFK_AIRPORT.properties.names, + display: expect.stringContaining('JFK') as string, + }, + }, + }, + NY_POLICE_AIRPORT, + LA_AIRPORT, + ], + expect + ) + ); + + tokenTypesUrlScope.done(); + }); + + test.each< + Pick & { + hierarchies: GenericGeocodingResponse['geocoding']['response']['hierarchies']; + returnedFeatures: MockLocationQueryFeature[]; + } + >([ + { + query: 'new york', + hierarchies: hierarchiesWithAnyWieght([NY_HIERRARCHY], expect), + returnedFeatures: [ + { + ...NY_JFK_AIRPORT, + properties: { + ...NY_JFK_AIRPORT.properties, + names: { + ...NY_JFK_AIRPORT.properties.names, + display: expect.stringContaining('JFK') as string, + }, + }, + }, + NY_POLICE_AIRPORT, + LA_AIRPORT, + ], + }, + { + query: 'los angeles', + hierarchies: hierarchiesWithAnyWieght([LA_HIERRARCHY], expect), + returnedFeatures: [ + LA_AIRPORT, + { + ...NY_JFK_AIRPORT, + properties: { + ...NY_JFK_AIRPORT.properties, + names: { + ...NY_JFK_AIRPORT.properties.names, + display: expect.stringContaining('JFK') as string, + }, + }, + }, + NY_POLICE_AIRPORT, + ], + }, + ])('it should test airports response with hierrarchy in %s', async ({ query, hierarchies, returnedFeatures }) => { + const requestParams: GetGeotextSearchParams = { query: `airport, ${query}`, limit: 5, disable_fuzziness: true }; + + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: ['airport'] }) + .reply(httpStatusCodes.OK, [ + { + tokens: ['airport'], + prediction: ['essence'], + }, + ]); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + + expect(response.body).toMatchObject>( + expectedResponse( + { + ...requestParams, + }, + { + place_types: ['transportation'], + sub_place_types: ['airport'], + hierarchies, + }, + returnedFeatures, + expect + ) + ); + + tokenTypesUrlScope.done(); + }); + + test.each< + Pick & { + place_types: GenericGeocodingResponse['geocoding']['response']['place_types']; + sub_place_types: GenericGeocodingResponse['geocoding']['response']['sub_place_types']; + returnedFeatures: MockLocationQueryFeature[]; + } + >([ + { + query: 'new york', + returnedFeatures: [NY_JFK_AIRPORT, NY_POLICE_AIRPORT, LA_AIRPORT, OSM_LA_PORT, GOOGLE_LA_PORT], + place_types: ['transportation', 'transportation'], + sub_place_types: ['airport', 'port'], + }, + { + query: 'los angeles', + place_types: ['transportation'], + sub_place_types: ['airport'], + returnedFeatures: [LA_AIRPORT, OSM_LA_PORT, GOOGLE_LA_PORT, NY_JFK_AIRPORT, NY_POLICE_AIRPORT], + }, + ])('it should test airports response with NLP Analyzer in %s', async ({ query, place_types, sub_place_types, returnedFeatures }) => { + const requestParams: GetGeotextSearchParams = { query: `airport ${query}`, limit: 5, disable_fuzziness: false }; + + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: requestParams.query.split(' ') }) + .reply(httpStatusCodes.OK, [ + { + tokens: requestParams.query.split(' '), + prediction: ['essence', 'name', 'name'], + }, + ]); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + + expect(response.body).toMatchObject>( + expectedResponse( + { + ...requestParams, + }, + { + name: query, + place_types, + sub_place_types, + hierarchies: [], + }, + returnedFeatures, + expect + ) + ); + + tokenTypesUrlScope.done(); + }); + + it('should return 200 status code and all regions', async function () { + const response = await requestSender.getRegions(); + + expect(response.status).toBe(httpStatusCodes.OK); + expect(response.body).toEqual(expect.arrayContaining(['USA'])); + // expect(response).toSatisfyApiSpec(); + }); + + it('should return 200 status code and all sources', async function () { + const response = await requestSender.getSources(); + + expect(response.status).toBe(httpStatusCodes.OK); + expect(response.body).toEqual(expect.arrayContaining(['OSM', 'GOOGLE'])); + // expect(response).toSatisfyApiSpec(); + }); + + it('should return 200 status code and ports from the corresponding source', async function () { + const requestParams: GetGeotextSearchParams = { query: 'port', source: ['google'], limit: 5, disable_fuzziness: true }; + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: requestParams.query.split(' ') }) + .reply(httpStatusCodes.OK, [ + { + tokens: ['port'], + prediction: ['essence'], + }, + ]); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + + expect(response.body).toMatchObject>( + expectedResponse( + requestParams, + { + place_types: ['transportation'], + sub_place_types: ['port'], + hierarchies: [], + }, + [GOOGLE_LA_PORT], + expect + ) + ); + + tokenTypesUrlScope.done(); + }); + + it('should return 200 status code and schools in specified region', async function () { + const requestParams: GetGeotextSearchParams = { query: 'school', region: ['france'], limit: 5, disable_fuzziness: true }; + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: requestParams.query.split(' ') }) + .reply(httpStatusCodes.OK, [ + { + tokens: ['school'], + prediction: ['essence'], + }, + ]); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.OK); + // expect(response).toSatisfyApiSpec(); + + expect(response.body).toMatchObject>( + expectedResponse( + requestParams, + { + place_types: ['education'], + sub_place_types: ['school'], + hierarchies: [], + }, + [PARIS_WI_SCHOOL], + expect + ) + ); + + tokenTypesUrlScope.done(); + }); + }); + + describe('Bad Path', function () { + // All requests with status code 4XX-5XX + test.each>([ + { + geo_context: { x: 300850, y: 4642203, zone: 33, radius: 100 }, + }, + { + geo_context: { lon: 12.598899687444742, lat: 41.90667824634701, radius: 10 }, + }, + { + geo_context: { bbox: [12.554407132912445, 41.84962590648513, 12.652837919839953, 41.94545380230761] }, + }, + { + geo_context_mode: GeoContextMode.BIAS, + }, + { + geo_context_mode: GeoContextMode.FILTER, + }, + ])('should return 400 and message that geo_context and geo_context_mode must be both defined or both undefined', async function (requestParams) { + const badRequestParams: GetGeotextSearchParams = { query: 'airport', limit: 5, disable_fuzziness: true, ...requestParams }; + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: badRequestParams.query.split(' ') }) + .reply(httpStatusCodes.OK, [ + { + tokens: ['airport'], + prediction: ['essence'], + }, + ]); + + const response = await requestSender.getQuery(badRequestParams); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: '/location/geotextQuery: geo_context and geo_context_mode must be both defined or both undefined', + }); + tokenTypesUrlScope.done(); + }); + }); + + describe('Sad Path', function () { + // All requests with status code 4XX-5XX + it('should return 500 status code when the NLP Analyzer service is down due to network error', async function () { + const errorMessage = 'NLP Analyzer service is down'; + const requestParams: GetGeotextSearchParams = { query: 'airport', limit: 5, disable_fuzziness: true }; + + // Intercept the request and simulate a network error + const nockScope = nock(config.get('application').services.tokenTypesUrl) + .post('') + .once() + .replyWithError({ message: errorMessage, code: 'ECONNREFUSED' }); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.INTERNAL_SERVER_ERROR); + expect(response.body).toHaveProperty('message', `NLP analyser is not available - ${errorMessage}`); + // expect(response).toSatisfyApiSpec(); + + nockScope.done(); + }); + + test.each<{ code: number; body: Body | undefined }>([ + { code: httpStatusCodes.OK, body: [] }, + { code: httpStatusCodes.OK, body: undefined }, + { code: httpStatusCodes.NO_CONTENT, body: { message: 'bad request' } }, + ])('should return 500 status code when the NLP Analyzer service not responding as expected', async function ({ code, body }) { + const requestParams: GetGeotextSearchParams = { query: 'airport', limit: 5, disable_fuzziness: false }; + + const nockScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: requestParams.query.split(' ') }) + .once() + .reply(code, body); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.INTERNAL_SERVER_ERROR); + + expect(response.body).toHaveProperty('message', expect.stringContaining('NLP analyser unexpected response:')); + + // expect(response).toSatisfyApiSpec(); + + nockScope.done(); + }); + + it('should return 400 status code when NLP Analyzer returns no tokens or prediction', async function () { + const requestParams: GetGeotextSearchParams = { query: 'airport', limit: 5, disable_fuzziness: false }; + + const nockScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: requestParams.query.split(' ') }) + .once() + .reply(httpStatusCodes.OK, [{ tokens: [], prediction: [] }]); + + const response = await requestSender.getQuery(requestParams); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toHaveProperty('message', 'No tokens or prediction'); + // expect(response).toSatisfyApiSpec(); + + nockScope.done(); + }); + + test.each<(keyof GeoContext)[][]>([[['lat', 'lon', 'radius']], [['x', 'y', 'zone', 'radius']]])( + 'should return 400 for all invalid geo_context object for the keys %s', + async function (keys) { + function generateCombinations(keys: (keyof GeoContext)[]): GeoContext[] { + const combinations: object[] = []; + + function backtrack(current: object, remainingKeys: string[]): void { + if (remainingKeys.length === 0) { + combinations.push(current); + return; + } + + const key = remainingKeys[0]; + const remaining = remainingKeys.slice(1); + + backtrack({ ...current, [key]: 1 }, remaining); + backtrack(current, remaining); + } + + backtrack({}, keys); + + return combinations; + } + + const geoContexts = generateCombinations(keys); + + for (const geo_context of geoContexts) { + if (Object.keys(geo_context).length === keys.length) { + continue; + } + const query = 'airport'; + const tokenTypesUrlScope = nock(config.get('application').services.tokenTypesUrl) + .post('', { tokens: query.split(' ') }) + .reply(httpStatusCodes.OK, [ + { + tokens: ['port'], + prediction: ['essence'], + }, + ]); + + const response = await requestSender.getQuery({ + query, + limit: 5, + disable_fuzziness: false, + geo_context: JSON.stringify(geo_context) as unknown as GetGeotextSearchParams['geo_context'], + geo_context_mode: GeoContextMode.BIAS, + }); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: + 'geo_context validation: geo_context must contain one of the following: {"bbox": [number,number,number,number] | [number,number,number,number,number,number]}, {"lat": number, "lon": number, "radius": number}, or {"x": number, "y": number, "zone": number, "radius": number}', + }); + + tokenTypesUrlScope.done(); + } + } + ); + }); +}); diff --git a/tests/integration/location/mockObjects.ts b/tests/integration/location/mockObjects.ts new file mode 100644 index 00000000..8fede417 --- /dev/null +++ b/tests/integration/location/mockObjects.ts @@ -0,0 +1,403 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +/* eslint-disable @typescript-eslint/no-magic-numbers */ +import { Feature } from 'geojson'; +import { GenericGeocodingResponse } from '../../../src/common/interfaces'; +import { HierarchySearchHit } from '../../../src/location/models/elasticsearchHits'; + +export type MockLocationQueryFeature = GenericGeocodingResponse['features'][number]; + +export const NY_JFK_AIRPORT: MockLocationQueryFeature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-73.81278266814672, 40.66039916690434], + [-73.8177430790069, 40.66160065836647], + [-73.82178645734075, 40.66043068890016], + [-73.82341214553732, 40.658185554886586], + [-73.82407909454082, 40.65496001884071], + [-73.8229536180974, 40.650532558586235], + [-73.82211993184252, 40.647939195437345], + [-73.81290769732489, 40.643985699842915], + [-73.79214887014153, 40.63414837731818], + [-73.78339516446982, 40.62987771430167], + [-73.7898562329419, 40.62275933562921], + [-73.78443726476769, 40.620069953803636], + [-73.7791433570518, 40.627188619100366], + [-73.77639219241223, 40.62706207167477], + [-73.77159849644941, 40.62336045339214], + [-73.77209870820208, 40.619975033140975], + [-73.77047302000595, 40.61908910045176], + [-73.76547094984971, 40.628422477310664], + [-73.75338249916041, 40.63291467256053], + [-73.74733827381596, 40.63601474373485], + [-73.7467963777506, 40.64208793530722], + [-73.752548854642, 40.64749646458006], + [-73.76213624656812, 40.65309424493557], + [-73.78181122379466, 40.66270746643491], + [-73.79106514121902, 40.66438330508498], + [-73.7957754685564, 40.665205399216404], + [-73.79856831750864, 40.66283394629252], + [-73.80390390953731, 40.66175885985783], + [-73.8073637074931, 40.66039916690434], + [-73.8109068740743, 40.66074699797244], + [-73.81278266814672, 40.66039916690434], + ], + ], + }, + properties: { + matches: [{ source: 'OSM', layer: 'osm_airports', source_id: ['03ed6d97-fc81-4340-b68a-11993554eef1'] }], + names: { + en: ['JFK Airport'], + fr: ['Aeropuerto JFK'], + default: ['JFK'], + display: 'JFK Airport', + }, + placetype: 'transportation', + sub_placetype: 'airport', + regions: [ + { + region: 'USA', + sub_region_names: ['New York'], + }, + ], + }, +}; + +export const NY_POLICE_AIRPORT: MockLocationQueryFeature = { + type: 'Feature', + geometry: { + coordinates: [ + [ + [-73.50019138870562, 40.76503398530525], + [-73.49991804403106, 40.75762191351754], + [-73.50172211928987, 40.75368779651572], + [-73.502924836068, 40.74958778448408], + [-73.50095675396722, 40.746067370663354], + [-73.49587254187276, 40.74117989901089], + [-73.48810955136324, 40.74010295019133], + [-73.4862508071566, 40.74097279482331], + [-73.48450140084462, 40.74209114977816], + [-73.48335335295225, 40.743250905424986], + [-73.49368578398266, 40.7500847690697], + [-73.49363111503533, 40.75112014169309], + [-73.49166303293454, 40.75087165373341], + [-73.49128035030373, 40.75435040065955], + [-73.48406404926651, 40.75385344795657], + [-73.4860321313673, 40.75840870869581], + [-73.50019138870562, 40.76503398530525], + ], + ], + type: 'Polygon', + }, + properties: { + matches: [{ source: 'OSM', layer: 'osm_airports', source_id: ['009c6b65-3dcb-4c4f-9f02-d766ebb5d808'] }], + names: { + en: ['Nassau County Police Airport'], + fr: ['Aeropuerto de la Policía del Condado de Nassau'], + default: ['Nassau County Police Airport'], + display: 'Nassau County Police Airport', + }, + placetype: 'transportation', + sub_placetype: 'airport', + regions: [ + { + region: 'USA', + sub_region_names: ['New York'], + }, + ], + }, +}; + +export const LA_AIRPORT: MockLocationQueryFeature = { + type: 'Feature', + geometry: { + coordinates: [ + [ + [-118.42713070992883, 33.9512236894319], + [-118.43440343023548, 33.94992163234602], + [-118.43571147345608, 33.94670980636225], + [-118.43560682999878, 33.943107208682775], + [-118.43325235220159, 33.938723119106925], + [-118.42875268352236, 33.9310829780122], + [-118.41394563426408, 33.931820976094315], + [-118.39756893251248, 33.932255089782316], + [-118.38516868278077, 33.935250412835686], + [-118.37904703905322, 33.936509285268926], + [-118.37899471732432, 33.943367642688045], + [-118.3788377519829, 33.94501703885841], + [-118.39181354073204, 33.94527746687625], + [-118.39510980964849, 33.945451085112225], + [-118.3980398264626, 33.94558129855595], + [-118.39746428744567, 33.949748023597365], + [-118.39720267894361, 33.953003135913036], + [-118.40813791735772, 33.95252573110099], + [-118.42713070992883, 33.9512236894319], + ], + ], + type: 'Polygon', + }, + properties: { + matches: [{ source: 'OSM', layer: 'osm_airports', source_id: ['a4f373ab-b824-41e2-b160-e7729c73bea6'] }], + names: { + en: ['Los Angeles International Airport'], + fr: ['Aeropuerto Internacional de Los Ángeles'], + default: ['Los Angeles International Airport'], + display: 'Los Angeles International Airport', + }, + placetype: 'transportation', + sub_placetype: 'airport', + regions: [ + { + region: 'USA', + sub_region_names: ['Los Angeles'], + }, + ], + }, +}; + +export const OSM_LA_PORT: MockLocationQueryFeature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-118.2505781304088, 33.7502674389752], + [-118.25604403409116, 33.76075151051916], + [-118.27057180697577, 33.748593059782564], + [-118.27503083555426, 33.741097783576635], + [-118.2747911028351, 33.734798055529765], + [-118.27215404292296, 33.73136889520775], + [-118.26807858669537, 33.720881194108856], + [-118.26424286318695, 33.721997816398385], + [-118.26640045650717, 33.72901625632974], + [-118.2431463824787, 33.735794882347946], + [-118.24492040460113, 33.739303607948656], + [-118.25072193640723, 33.73794798097781], + [-118.25220827926702, 33.74193505797223], + [-118.24937943317966, 33.74508471776615], + [-118.24798898340768, 33.74783559181691], + [-118.24909175391655, 33.74803492708783], + [-118.25096166912684, 33.74600168558719], + [-118.25326310323155, 33.745363795966625], + [-118.25278363779321, 33.74687877606813], + [-118.2505781304088, 33.7502674389752], + ], + ], + }, + properties: { + matches: [{ source: 'OSM', layer: 'osm_ports', source_id: ['0f36d985-cfbd-4aed-b0cb-ee56600c77f4'] }], + names: { + en: ['Port of Los Angeles'], + fr: ['Puerto de Los Ángeles'], + default: ['Port of Los Angeles'], + display: 'Port of Los Angeles', + }, + placetype: 'transportation', + sub_placetype: 'port', + regions: [ + { + region: 'USA', + sub_region_names: ['Los Angeles'], + }, + ], + }, +}; +export const GOOGLE_LA_PORT: MockLocationQueryFeature = { + type: 'Feature', + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-118.2505781304088, 33.7502674389752], + [-118.25604403409116, 33.76075151051916], + [-118.27057180697577, 33.748593059782564], + [-118.27503083555426, 33.741097783576635], + [-118.2747911028351, 33.734798055529765], + [-118.27215404292296, 33.73136889520775], + [-118.26807858669537, 33.720881194108856], + [-118.26424286318695, 33.721997816398385], + [-118.26640045650717, 33.72901625632974], + [-118.2431463824787, 33.735794882347946], + [-118.24492040460113, 33.739303607948656], + [-118.25072193640723, 33.73794798097781], + [-118.25220827926702, 33.74193505797223], + [-118.24937943317966, 33.74508471776615], + [-118.24798898340768, 33.74783559181691], + [-118.24909175391655, 33.74803492708783], + [-118.25096166912684, 33.74600168558719], + [-118.25326310323155, 33.745363795966625], + [-118.25278363779321, 33.74687877606813], + [-118.2505781304088, 33.7502674389752], + ], + ], + }, + properties: { + matches: [{ source: 'GOOGLE', layer: 'google_ports', source_id: ['1bb11f54-939e-457b-bf68-a3920ccf629c'] }], + names: { + en: ['Port of Los Angeles'], + fr: ['Puerto de Los Ángeles'], + default: ['Port of Los Angeles'], + display: 'Port of Los Angeles', + }, + placetype: 'transportation', + sub_placetype: 'port', + regions: [ + { + region: 'USA', + sub_region_names: ['Los Angeles'], + }, + ], + }, +}; + +export const LA_WHITE_POINT_SCHOOL: MockLocationQueryFeature = { + type: 'Feature', + geometry: { + coordinates: [ + [ + [-118.30812263653988, 33.71684417247593], + [-118.30861990876181, 33.71674433152869], + [-118.30879709771484, 33.71635922964194], + [-118.30619642115158, 33.71550819588987], + [-118.30586490633668, 33.715921827872904], + [-118.30587062210924, 33.716183318328746], + [-118.30812263653988, 33.71684417247593], + ], + ], + type: 'Polygon', + }, + properties: { + matches: [ + { + source: 'OSM', + layer: 'osm_schools', + source_id: ['1a5b981b-bb0e-44dd-b9e2-424b92f2de49'], + }, + ], + names: { + en: ['White Point Elementary School'], + fr: ['Escuela Primaria White Point'], + default: ['White Point Elementary School'], + display: 'White Point Elementary School', + }, + placetype: 'education', + sub_placetype: 'school', + regions: [ + { + region: 'USA', + sub_region_names: ['Los Angeles'], + }, + ], + }, +}; + +export const PARIS_WI_SCHOOL: MockLocationQueryFeature = { + type: 'Feature', + geometry: { + coordinates: [ + [ + [2.346441270696971, 48.88088750665477], + [2.3462780852304945, 48.88018258877358], + [2.347503576087604, 48.87999951892243], + [2.347737155284733, 48.88070864783427], + [2.346441270696971, 48.88088750665477], + ], + ], + type: 'Polygon', + }, + properties: { + matches: [ + { + source: 'OSM', + layer: 'osm_schools', + source_id: ['dc02a3f9-156a-4f61-85bd-fd040cd322a3'], + }, + ], + names: { + en: ['Wi School Paris 9'], + fr: ['Ecole Wi Paris 9'], + default: ['Wi School Paris 9'], + display: 'Wi School Paris 9', + }, + placetype: 'education', + sub_placetype: 'school', + regions: [ + { + region: 'FRANCE', + sub_region_names: ['Paris'], + }, + ], + }, +}; + +export const NY_HIERRARCHY: HierarchySearchHit = { + geo_json: { + coordinates: [ + [ + [-73.74286189030825, 40.566325396473786], + [-73.47084009765854, 40.56212896709357], + [-73.550927745189, 41.11163279131463], + [-73.74424271181776, 41.225972287315074], + [-73.99969469100891, 41.26438691280978], + [-74.24962338416366, 41.05959508414017], + [-74.13087273437748, 40.7506852054762], + [-74.00659879855483, 40.530651727069795], + [-73.74286189030825, 40.566325396473786], + ], + ], + type: 'Polygon', + }, + hierarchy: 'city', + placetype: 'city', + region: 'USA', + text: 'New York', + weight: 1.1, +}; + +export const LA_HIERRARCHY: HierarchySearchHit = { + geo_json: { + coordinates: [ + [ + [-118.54430957638033, 34.07939240620722], + [-118.5350828996408, 33.695192367610986], + [-118.04596133238863, 33.47690745532634], + [-117.66265886139905, 33.379872950239346], + [-117.57145361502153, 33.63336904289318], + [-117.67279277766329, 34.23871934668085], + [-118.2605599209839, 34.28059749364003], + [-118.54430957638033, 34.07939240620722], + ], + ], + type: 'Polygon', + }, + hierarchy: 'city', + placetype: 'city', + region: 'USA', + text: 'Los Angeles', + weight: 1.1, +}; + +export const PARIS_HIERRARCHY: HierarchySearchHit = { + geo_json: { + coordinates: [ + [ + [2.226678539753607, 49.06838747927134], + [1.9344166918067742, 48.906487548202136], + [2.014124468519668, 48.56855190252173], + [2.6536844864307625, 48.53463335095324], + [2.902296837606201, 48.82159183126478], + [2.6460932696008683, 49.0124047223114], + [2.384196288972504, 49.05097737411208], + [2.226678539753607, 49.06838747927134], + ], + ], + type: 'Polygon', + }, + hierarchy: 'city', + placetype: 'city', + region: 'FRANCE', + text: 'Paris', + weight: 1.1, +}; diff --git a/tests/integration/location/utils.ts b/tests/integration/location/utils.ts new file mode 100644 index 00000000..ee83b5d0 --- /dev/null +++ b/tests/integration/location/utils.ts @@ -0,0 +1,46 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Feature } from 'geojson'; +import { GetGeotextSearchParams } from '../../../src/location/interfaces'; +import { GenericGeocodingResponse } from '../../../src/common/interfaces'; +import { MockLocationQueryFeature } from './mockObjects'; + +const expectedObjectWithScore = (obj: MockLocationQueryFeature, expect: jest.Expect): GenericGeocodingResponse['features'][number] => + ({ + ...obj, + properties: { + ...obj.properties, + score: expect.any(Number) as number, + }, + } as GenericGeocodingResponse['features'][number]); + +const expectedGeocodingElasticResponseMetrics = ( + responseParams: Partial['geocoding']['response']>, + resultsCount: number, + expect: jest.Expect +): NonNullable['geocoding']>['response'] => ({ + results_count: resultsCount, + max_score: expect.any(Number) as number, + match_latency_ms: expect.any(Number) as number, + ...responseParams, +}); + +export const expectedResponse = ( + requestParams: GetGeotextSearchParams, + responseParams: Partial['geocoding']['response']>, + arr: MockLocationQueryFeature[], + expect: jest.Expect +): GenericGeocodingResponse => ({ + type: 'FeatureCollection', + geocoding: { + version: process.env.npm_package_version, + query: requestParams, + response: expectedGeocodingElasticResponseMetrics(responseParams, arr.length, expect), + }, + features: arr.map((item) => expectedObjectWithScore(item, expect)), +}); + +export const hierarchiesWithAnyWieght = ( + hierarchies: GenericGeocodingResponse['geocoding']['response']['hierarchies'], + expect: jest.Expect +): GenericGeocodingResponse['geocoding']['response']['hierarchies'] => + (hierarchies as { hierarchy: string }[] | undefined)?.map((hierarchy) => ({ ...hierarchy, weight: expect.any(Number) as number })); diff --git a/tests/integration/mgrs/helpers/requestSender.ts b/tests/integration/mgrs/helpers/requestSender.ts new file mode 100644 index 00000000..f12c9483 --- /dev/null +++ b/tests/integration/mgrs/helpers/requestSender.ts @@ -0,0 +1,16 @@ +import * as supertest from 'supertest'; +import { GetTileQueryParams } from '../../../../src/mgrs/controllers/mgrsController'; + +export class MgrsRequestSender { + public constructor(private readonly app: Express.Application) {} + + public async getTile(queryParams?: GetTileQueryParams): Promise { + return supertest + .agent(this.app) + .get('/search/MGRS/tiles') + .set('Content-Type', 'application/json') + .set('x-api-key', 'abc123') + .set('x-user-id', 'abc123') + .query(queryParams ?? {}); + } +} diff --git a/tests/integration/mgrs/mgrs.spec.ts b/tests/integration/mgrs/mgrs.spec.ts new file mode 100644 index 00000000..eb126aa8 --- /dev/null +++ b/tests/integration/mgrs/mgrs.spec.ts @@ -0,0 +1,112 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import config from 'config'; +import { DependencyContainer } from 'tsyringe'; +import { Application } from 'express'; +import { CleanupRegistry } from '@map-colonies/cleanup-registry'; +import jsLogger from '@map-colonies/js-logger'; +import { trace } from '@opentelemetry/api'; +import httpStatusCodes from 'http-status-codes'; +import { getApp } from '../../../src/app'; +import { SERVICES } from '../../../src/common/constants'; +import { S3_REPOSITORY_SYMBOL } from '../../../src/common/s3/s3Repository'; +import { cronLoadTileLatLonDataSymbol } from '../../../src/latLon/DAL/latLonDAL'; +import { MgrsRequestSender } from './helpers/requestSender'; + +describe('/search/MGRS', function () { + let requestSender: MgrsRequestSender; + let app: { app: Application; container: DependencyContainer }; + + beforeEach(async function () { + app = await getApp({ + override: [ + { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, + { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, + { token: S3_REPOSITORY_SYMBOL, provider: { useValue: {} } }, + { token: SERVICES.S3_CLIENT, provider: { useValue: {} } }, + { token: cronLoadTileLatLonDataSymbol, provider: { useValue: {} } }, + { token: SERVICES.ELASTIC_CLIENTS, provider: { useValue: {} } }, + ], + useChild: true, + }); + + requestSender = new MgrsRequestSender(app.app); + }); + + afterAll(async function () { + const cleanupRegistry = app.container.resolve(SERVICES.CLEANUP_REGISTRY); + await cleanupRegistry.trigger(); + app.container.reset(); + + jest.clearAllTimers(); + }); + + describe('Happy Path', function () { + it('should return 200 status code and MGRS in geojson', async function () { + const response = await requestSender.getTile({ tile: '18SUJ2339007393' }); + + expect(response.status).toBe(httpStatusCodes.OK); + expect(response).toSatisfyApiSpec(); + expect(response.body).toMatchObject({ + type: 'Feature', + geocoding: { + query: { + tile: '18SUJ2339007393', + }, + response: { + max_score: 1, + results_count: 1, + match_latency_ms: 0, + }, + }, + bbox: [-77.03654883669269, 38.89767541638445, -77.03653756947197, 38.897684623284015], + geometry: { + type: 'Polygon', + coordinates: [ + [ + [-77.03654883669269, 38.89767541638445], + [-77.03654883669269, 38.897684623284015], + [-77.03653756947197, 38.897684623284015], + [-77.03653756947197, 38.89767541638445], + [-77.03654883669269, 38.89767541638445], + ], + ], + }, + properties: { + score: 1, + }, + }); + }); + }); + + describe('Bad Path', function () { + it('should return 400 status code when MGRS is invalid', async function () { + const response = await requestSender.getTile({ tile: 'ABC{}' }); + + expect(response.body).toMatchObject({ + message: 'Invalid MGRS tile', + }); + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + }); + + it('should return 400 status code when MGRS is missing', async function () { + const response = await requestSender.getTile(); + + expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); + expect(response.body).toMatchObject({ + message: "request/query must have required property 'tile'", + }); + }); + }); + + describe('Sad Path', function () { + // All requests with status code 4XX-5XX + it('should return 500 status code when MGRSPoint zone letter A not handled', async function () { + const response = await requestSender.getTile({ tile: '{ABC}' }); + + expect(response.status).toBe(httpStatusCodes.INTERNAL_SERVER_ERROR); + expect(response.body).toMatchObject({ + message: 'MGRSPoint zone letter A not handled: {ABC}', + }); + }); + }); +}); diff --git a/tests/integration/route/route.spec.ts b/tests/integration/route/route.spec.ts deleted file mode 100644 index 6e195f95..00000000 --- a/tests/integration/route/route.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import jsLogger from '@map-colonies/js-logger'; -import { trace } from '@opentelemetry/api'; -import httpStatusCodes from 'http-status-codes'; -import { getApp } from '../../../src/app'; -import { SERVICES } from '../../../src/common/constants'; -import { GetRoutesQueryParams } from '../../../src/route/controllers/routeController'; -import { RouteRequestSender } from './helpers/requestSender'; - -describe('/routes', function () { - let requestSender: RouteRequestSender; - - beforeEach(async function () { - const app = await getApp({ - override: [ - { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, - { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, - ], - useChild: true, - }); - requestSender = new RouteRequestSender(app.app); - }); - - describe('Happy Path', function () { - it('should return 200 status code and the route', async function () { - const response = await requestSender.getRoutes({ command_name: 'route96' }); - - expect(response.status).toBe(httpStatusCodes.OK); - expect(response).toSatisfyApiSpec(); - }); - it('should return 200 status code and empty response', async function () { - const response = await requestSender.getRoutes({ command_name: '48054805' }); - - expect(response.status).toBe(httpStatusCodes.OK); - - expect(response).toSatisfyApiSpec(); - expect(response.body).toMatchObject({ - type: 'FeatureCollection', - features: [], - }); - }); - }); - describe('Bad Path', function () { - // All requests with status code of 400 - it('Should return 400 status code and meessage "request/query must have required property \'command_name\'"', async function () { - const message = "request/query must have required property 'command_name'"; - - const response = await requestSender.getRoutes(); - - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ message }); - expect(response).toSatisfyApiSpec(); - }); - it('Should return 400 status code for unknown parameter', async function () { - const parameter = 'test1234'; - const message = `Unknown query parameter '${parameter}'`; - - const response = await requestSender.getRoutes({ [parameter]: parameter } as unknown as GetRoutesQueryParams); - - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ message }); - expect(response).toSatisfyApiSpec(); - }); - }); - describe('Sad Path', function () { - // All requests with status code 4XX-5XX - }); -}); diff --git a/tests/integration/tile/tile.spec.ts b/tests/integration/tile/tile.spec.ts deleted file mode 100644 index abd5bd6e..00000000 --- a/tests/integration/tile/tile.spec.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import jsLogger from '@map-colonies/js-logger'; -import { trace } from '@opentelemetry/api'; -import httpStatusCodes from 'http-status-codes'; -import { getApp } from '../../../src/app'; -import { SERVICES } from '../../../src/common/constants'; -import { GetTilesQueryParams } from '../../../src/tile/controllers/tileController'; -import { TileRequestSender } from './helpers/requestSender'; - -describe('/tiles', function () { - let requestSender: TileRequestSender; - - beforeEach(async function () { - const app = await getApp({ - override: [ - { token: SERVICES.LOGGER, provider: { useValue: jsLogger({ enabled: false }) } }, - { token: SERVICES.TRACER, provider: { useValue: trace.getTracer('testTracer') } }, - ], - useChild: true, - }); - requestSender = new TileRequestSender(app.app); - }); - - describe('Happy Path', function () { - it('should return 200 status code and the tile', async function () { - const response = await requestSender.getTiles({ tile: 'RIT' }); - - expect(response.status).toBe(httpStatusCodes.OK); - expect(response).toSatisfyApiSpec(); - expect(response.body).toMatchObject({ - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - coordinates: [ - [ - [12.539507865186607, 41.851751203650096], - [12.536787075186538, 41.94185043165008], - [12.42879133518656, 41.93952837265009], - [12.431625055186686, 41.84943698365008], - [12.539507865186607, 41.851751203650096], - ], - ], - type: 'Polygon', - }, - properties: { - TILE_NAME: 'RIT', - TYPE: 'TILE', - }, - }, - ], - }); - }); - - it('should return 200 status code and the subtile', async function () { - const response = await requestSender.getTiles({ tile: 'GRC', sub_tile: '65' }); - - expect(response.status).toBe(httpStatusCodes.OK); - expect(response).toSatisfyApiSpec(); - expect(response.body).toMatchObject({ - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - coordinates: [ - [ - [27.149158174343427, 35.63159611670335], - [27.149274355343437, 35.64061707270338], - [27.138786228343463, 35.640716597703374], - [27.13867103934342, 35.631695606703374], - [27.149158174343427, 35.63159611670335], - ], - ], - type: 'Polygon', - }, - properties: { - SUB_TILE_ID: '65', - TILE_NAME: 'GRC', - TYPE: 'SUB_TILE', - }, - }, - ], - }); - }); - - it('should return 200 status code and response empty array', async function () { - const response = await requestSender.getTiles({ tile: 'xyz' }); - - expect(response.status).toBe(httpStatusCodes.OK); - expect(response).toSatisfyApiSpec(); - expect(response.body).toMatchObject({ - type: 'FeatureCollection', - features: [], - }); - }); - }); - describe('Bad Path', function () { - // All requests with status code of 400 - it('Should return 400 status code and meessage "request/query must have required property \'tile\'"', async function () { - const message = "request/query must have required property 'tile'"; - - const response = await requestSender.getTiles(); - - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ message }); - expect(response).toSatisfyApiSpec(); - }); - it('Should return 400 status code for unknown parameter', async function () { - const parameter = 'test1234'; - const message = `Unknown query parameter '${parameter}'`; - - const response = await requestSender.getTiles({ [parameter]: parameter } as unknown as GetTilesQueryParams); - - expect(response.status).toBe(httpStatusCodes.BAD_REQUEST); - expect(response.body).toMatchObject({ message }); - expect(response).toSatisfyApiSpec(); - }); - }); - describe('Sad Path', function () { - // All requests with status code 4XX-5XX - }); -}); diff --git a/tests/unit/common/objects.ts b/tests/unit/common/objects.ts deleted file mode 100644 index 3ef7ec5b..00000000 --- a/tests/unit/common/objects.ts +++ /dev/null @@ -1,271 +0,0 @@ -/* eslint-disable @typescript-eslint/no-magic-numbers */ -/* eslint-disable @typescript-eslint/naming-convention */ -import { estypes } from '@elastic/elasticsearch'; -import { FeatureCollection } from 'geojson'; - -/* ------------------- Objects for testing common/utils.ts: formatResponse() - ITEM type -------------------*/ -export const itemElasticResponse: Pick = { - hits: { - total: { value: 1, relation: 'eq' }, - max_score: 1.89712, - hits: [ - { - _index: 'control_gil_v5', - _id: 'CONTROL.ITEMS', - _score: 1.89712, - _source: { - type: 'Feature', - geometry: { - coordinates: [ - [ - [98.96871358832425, 18.77187541003238], - [98.96711613001014, 18.772012912146167], - [98.9668257091372, 18.77957500633211], - [98.96517988589727, 18.783516234000658], - [98.96305002071, 18.786174113473763], - [98.96222710028087, 18.786036643850125], - [98.9615009529004, 18.784478609414165], - [98.96096850521184, 18.78324114944766], - [98.96058105300438, 18.74932410944021], - [98.96029062202649, 18.747169677704846], - [98.9619364723165, 18.74666541646775], - [98.96479250629358, 18.7514784826093], - [98.96401797557468, 18.75193687161918], - [98.96614791571238, 18.754412116891245], - [98.96639000300826, 18.75940841151673], - [98.96779381973744, 18.759133392649602], - [98.96798745662056, 18.76018763174328], - [98.96789063809456, 18.761746062357858], - [98.96905242991687, 18.763487833908357], - [98.96871358832425, 18.77187541003238], - ], - ], - type: 'Polygon', - }, - properties: { - OBJECT_COMMAND_NAME: '4805', - TILE_NAME: 'DEF', - SUB_TILE_ID: '36', - ENTITY_HEB: 'airport', - TYPE: 'ITEM', - }, - }, - }, - ], - }, -}; - -export const itemExpectedFormattedResponse: FeatureCollection = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - coordinates: [ - [ - [98.96871358832425, 18.77187541003238], - [98.96711613001014, 18.772012912146167], - [98.9668257091372, 18.77957500633211], - [98.96517988589727, 18.783516234000658], - [98.96305002071, 18.786174113473763], - [98.96222710028087, 18.786036643850125], - [98.9615009529004, 18.784478609414165], - [98.96096850521184, 18.78324114944766], - [98.96058105300438, 18.74932410944021], - [98.96029062202649, 18.747169677704846], - [98.9619364723165, 18.74666541646775], - [98.96479250629358, 18.7514784826093], - [98.96401797557468, 18.75193687161918], - [98.96614791571238, 18.754412116891245], - [98.96639000300826, 18.75940841151673], - [98.96779381973744, 18.759133392649602], - [98.96798745662056, 18.76018763174328], - [98.96789063809456, 18.761746062357858], - [98.96905242991687, 18.763487833908357], - [98.96871358832425, 18.77187541003238], - ], - ], - type: 'Polygon', - }, - properties: { - OBJECT_COMMAND_NAME: '4805', - TILE_NAME: 'DEF', - SUB_TILE_ID: '36', - ENTITY_HEB: 'airport', - TYPE: 'ITEM', - }, - }, - ], -}; - -/* ------------------- Objects for testing common/utils.ts: formatResponse() - TILE type -------------------*/ - -export const tileElasticResponse: Pick = { - hits: { - total: { value: 1, relation: 'eq' }, - max_score: 2.184802, - hits: [ - { - _index: 'control_gil_v5', - _id: 'CONTROL.TILES', - _score: 2.184802, - _source: { - type: 'Feature', - geometry: { - coordinates: [ - [ - [12.539507865186607, 41.851751203650096], - [12.536787075186538, 41.94185043165008], - [12.42879133518656, 41.93952837265009], - [12.431625055186686, 41.84943698365008], - [12.539507865186607, 41.851751203650096], - ], - ], - type: 'Polygon', - }, - properties: { TILE_NAME: 'RIT', TYPE: 'TILE' }, - }, - }, - ], - }, -}; - -export const tileExpectedFormattedResponse: FeatureCollection = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - coordinates: [ - [ - [12.539507865186607, 41.851751203650096], - [12.536787075186538, 41.94185043165008], - [12.42879133518656, 41.93952837265009], - [12.431625055186686, 41.84943698365008], - [12.539507865186607, 41.851751203650096], - ], - ], - type: 'Polygon', - }, - properties: { TILE_NAME: 'RIT', TYPE: 'TILE' }, - }, - ], -}; - -/* ------------------- Objects for testing common/utils.ts: formatResponse() - SUB_TILE type -------------------*/ - -export const subTileElasticResponse: Pick = { - hits: { - total: { value: 1, relation: 'eq' }, - max_score: 2.877949, - hits: [ - { - _index: 'control_gil_v5', - _id: 'CONTROL.SUB_TILES', - _score: 2.877949, - _source: { - type: 'Feature', - geometry: { - coordinates: [ - [ - [27.149158174343427, 35.63159611670335], - [27.149274355343437, 35.64061707270338], - [27.138786228343463, 35.640716597703374], - [27.13867103934342, 35.631695606703374], - [27.149158174343427, 35.63159611670335], - ], - ], - type: 'Polygon', - }, - properties: { SUB_TILE_ID: '65', TILE_NAME: 'GRC', TYPE: 'SUB_TILE' }, - }, - }, - ], - }, -}; -export const subTileExpectedFormattedResponse: FeatureCollection = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - coordinates: [ - [ - [27.149158174343427, 35.63159611670335], - [27.149274355343437, 35.64061707270338], - [27.138786228343463, 35.640716597703374], - [27.13867103934342, 35.631695606703374], - [27.149158174343427, 35.63159611670335], - ], - ], - type: 'Polygon', - }, - properties: { SUB_TILE_ID: '65', TILE_NAME: 'GRC', TYPE: 'SUB_TILE' }, - }, - ], -}; - -/* ------------------- Objects for testing common/utils.ts: formatResponse() - ROUTE type -------------------*/ - -export const routeElasticResponse: Pick = { - hits: { - total: { value: 1, relation: 'eq' }, - max_score: 1.89712, - hits: [ - { - _index: 'control_gil_v5', - _id: 'CONTROL.ROUTES', - _score: 1.89712, - _source: { - type: 'Feature', - geometry: { - coordinates: [ - [13.448493352142947, 52.31016611400918], - [13.447219581381603, 52.313370282889224], - [13.448088381125075, 52.31631514453963], - [13.450458681234068, 52.31867376333767], - [13.451112278530388, 52.32227665244022], - [13.449728938644029, 52.32463678850752], - [13.445021899434977, 52.32863442881066], - [13.444723882330948, 52.340023400115086], - [13.446229682887974, 52.34532799609971], - ], - type: 'LineString', - }, - properties: { - OBJECT_COMMAND_NAME: 'route96', - ENTITY_HEB: 'route96', - TYPE: 'ROUTE', - }, - }, - }, - ], - }, -}; -export const routeExpectedFormattedResponse: FeatureCollection = { - type: 'FeatureCollection', - features: [ - { - type: 'Feature', - geometry: { - coordinates: [ - [13.448493352142947, 52.31016611400918], - [13.447219581381603, 52.313370282889224], - [13.448088381125075, 52.31631514453963], - [13.450458681234068, 52.31867376333767], - [13.451112278530388, 52.32227665244022], - [13.449728938644029, 52.32463678850752], - [13.445021899434977, 52.32863442881066], - [13.444723882330948, 52.340023400115086], - [13.446229682887974, 52.34532799609971], - ], - type: 'LineString', - }, - properties: { - OBJECT_COMMAND_NAME: 'route96', - ENTITY_HEB: 'route96', - TYPE: 'ROUTE', - }, - }, - ], -}; diff --git a/tests/unit/common/utils.spec.ts b/tests/unit/common/utils.spec.ts deleted file mode 100644 index 1913381b..00000000 --- a/tests/unit/common/utils.spec.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { estypes } from '@elastic/elasticsearch'; -import { - formatResponse, - additionalSearchProperties, - convertUTMToWgs84, - convertWgs84ToUTM, - validateTile, - validateWGS84Coordinate, -} from '../../../src/common/utils'; -import config from '../../../config/test.json'; -import { FIELDS } from '../../../src/common/constants'; -import { WGS84Coordinate } from '../../../src/common/interfaces'; -import { - itemElasticResponse, - itemExpectedFormattedResponse, - tileElasticResponse, - tileExpectedFormattedResponse, - subTileElasticResponse, - subTileExpectedFormattedResponse, - routeElasticResponse, - routeExpectedFormattedResponse, -} from './objects'; - -describe('utils', () => { - test.each([ - [itemElasticResponse, itemExpectedFormattedResponse], - [tileElasticResponse, tileExpectedFormattedResponse], - [subTileElasticResponse, subTileExpectedFormattedResponse], - [routeElasticResponse, routeExpectedFormattedResponse], - ])('should convert ElasticSearch query response to FeatureCollection', (elasticResponse, formattedResponse) => { - const result = formatResponse(elasticResponse as estypes.SearchResponse); - expect(result).toMatchObject(formattedResponse); - }); - - it('should return additional search properties', () => { - const size = 10; - const result = additionalSearchProperties(size); - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - expect(result).toMatchObject({ size, index: config.db.elastic.searchy.properties.index as string, _source: FIELDS }); - }); - - it('should convert UTM to WGS84', () => { - const result = convertUTMToWgs84(630048, 4330433, 29); - expect(result).toMatchObject({ lat: 39.11335578352079, lon: -7.495780486809503 }); - }); - - it('should convert WGS84 to UTM', () => { - const result = convertWgs84ToUTM(39.11335712352982, -7.495784527093747); - expect(result).toMatchObject({ Easting: 630048, Northing: 4330433, ZoneNumber: 29 }); - }); - - test.each([ - [{ tileName: 'BRN', subTileNumber: [10, 10, 10] }, true], - [{ tileName: 'BRN', subTileNumber: [0, 10, 10] }, false], - [{ tileName: 'BRN', subTileNumber: [10, 0, 10] }, false], - [{ tileName: 'BRN', subTileNumber: [10, 10, 0] }, false], - [{ tileName: 'BRN', subTileNumber: [10, 10, 100] }, false], - [{ tileName: 'BRN', subTileNumber: [10, 10] }, false], - [{ tileName: '', subTileNumber: [10, 10, 10] }, false], - ])(`should validate tile`, (tile, expected) => { - expect(validateTile(tile as never)).toBe(expected); - }); - - test.each<[WGS84Coordinate, boolean]>([ - [{ lon: 50, lat: 50 }, true], - [{ lon: 190, lat: 50 }, false], - [{ lon: 50, lat: 190 }, false], - [{ lon: -10, lat: 50 }, false], - [{ lon: 50, lat: -10 }, false], - ])('should validate WGS84 coordinate', (coordinate, expected) => { - expect(validateWGS84Coordinate(coordinate as never)).toBe(expected); - }); -});