From 08c5c91a19d82504908dddc034e02d5ff83e54a2 Mon Sep 17 00:00:00 2001 From: Tim Fabian Date: Fri, 29 May 2026 02:51:05 +0200 Subject: [PATCH 1/8] added versioning system, refactored open api, added reInit method to started test server, updated type of required:false properties to also be null --- docs/creating-endpoints.md | 4 +- docs/plugin.md | 3 +- package-lock.json | 1478 +++++++++-------- package.json | 36 +- sandbox/assets/public/open-api/custom.css | 26 +- sandbox/assets/public/open-api/swagger-ui.css | 4 +- sandbox/package-lock.json | 146 +- sandbox/package.json | 2 +- sandbox/src/controllers/cron.controller.ts | 2 +- sandbox/src/controllers/file.controller.ts | 2 +- sandbox/src/controllers/metrics.controller.ts | 2 +- sandbox/src/controllers/page.controller.ts | 2 +- .../src/controllers/template.controller.ts | 2 +- .../src/controllers/test-crud.controller.ts | 2 +- sandbox/src/controllers/test.controller.ts | 2 +- .../controllers/test.websocket-controller.ts | 2 +- sandbox/src/index.ts | 4 +- .../src/models/generated/petstore/index.ts | 4 +- .../petstore/petstore.api-response.model.ts | 6 +- .../petstore/petstore.category.model.ts | 4 +- .../petstore/petstore.order.model.ts | 12 +- .../generated/petstore/petstore.pet.model.ts | 17 +- .../generated/petstore/petstore.tag.model.ts | 4 +- .../petstore.update-pet-with-form.model.ts | 10 - .../petstore/petstore.upload-file.model.ts | 10 - .../generated/petstore/petstore.user.model.ts | 16 +- sandbox/src/models/user.model.ts | 4 +- sandbox/versions/1.0.0.json | 1 + sandbox/versions/1.0.1.json | 1 + sandbox/webpack.config.js | 10 +- .../test-server/start-test-server.function.ts | 68 +- src/application-options.model.ts | 4 +- src/application.ts | 1 - src/assets/asset.service.ts | 4 +- .../2fa/methods/otp/otp-credentials.model.ts | 2 +- .../2fa/methods/otp/otp.two-factor-method.ts | 2 +- .../cookie/cookie-auth.controller.ts | 2 +- .../strategies/jwt/jwt-auth.controller.ts | 2 +- src/auth/strategies/jwt/jwt.auth-strategy.ts | 2 +- src/backup/backup-resource-entity.model.ts | 2 +- src/backup/backup-service.test.ts | 3 +- src/change-sets/models/change-set.model.ts | 2 +- src/change-sets/models/change.model.ts | 4 +- src/context/cache/cache.context.ts | 4 +- .../request/request-context-token.model.ts | 2 +- src/cron/cron-job-entity.model.ts | 4 +- .../typeorm-base-data-source.model.ts | 9 +- .../typeorm-where-filter.converter.ts | 3 +- src/data-source/hooks/hooks.test.ts | 2 +- .../migration/migration-entity.model.ts | 4 +- src/data-source/migration/migration.model.ts | 4 +- src/data-source/migration/migration.test.ts | 4 +- .../models/where/date-where-filter.model.ts | 14 +- .../models/where/where-filter-keys.model.ts | 2 + src/data-source/nested-where-filter.test.ts | 4 +- src/data-source/repository.test.ts | 4 +- src/data-source/where-filter.test.ts | 7 - src/di/default/zibri-di-providers.default.ts | 4 + src/di/default/zibri-di-tokens.default.ts | 3 + src/email/email.service.ts | 10 +- src/email/models/email.model.ts | 8 +- .../generate-entity-file.function.ts | 2 +- src/event/event-subscriber-run.model.ts | 2 +- src/global/global-registry.ts | 4 +- src/http-client/http-client.ts | 1 - src/http/http-request.model.ts | 4 +- src/http/known-header.enum.ts | 1 + src/index.ts | 12 +- src/logging/log-context.model.ts | 12 +- src/logging/logger.ts | 2 +- .../models/thread-job-entity.model.ts | 10 +- src/multithreading/services/thread-job.ts | 8 +- src/open-api/open-api-service.interface.ts | 3 +- src/open-api/open-api.service.ts | 395 +++-- src/parsing/html/csp-options.model.ts | 3 + src/parsing/parser.ts | 6 +- .../invoicing/models/invoice-address.model.ts | 4 +- src/plugin/invoicing/models/vat.model.ts | 2 +- .../mailing-list/mailing-list.controller.ts | 2 +- .../models/mailing-list-subscriber.model.ts | 2 +- ...t-subscription-confirmation-token.model.ts | 2 +- src/plugin/payment/models/payment.model.ts | 4 +- .../providers/pay-pal/pay-pal-client.ts | 34 +- .../pay-pal/pay-pal.payment-provider.test.ts | 2 +- .../pay-pal/pay-pal.payment-provider.ts | 2 +- src/preact/preact.utilities.ts | 2 +- .../validate-email-templates.function.ts | 2 +- src/rate-limiting/rate-limiter.ts | 33 +- .../controller-route-configuration.model.ts | 7 +- .../decorators/controller.decorator.ts | 10 +- .../create-http-decorator.function.ts | 5 +- src/routing/decorators/delete.decorator.ts | 6 +- src/routing/decorators/get.decorator.ts | 6 +- .../http-decorator-option-input.model.ts | 11 + src/routing/decorators/patch.decorator.ts | 6 +- src/routing/decorators/post.decorator.ts | 6 +- src/routing/resolve-route-params.function.ts | 1 + src/routing/route-configuration.model.ts | 16 +- src/routing/router.ts | 399 +++-- src/types/version.type.ts | 4 - src/utilities/compare-versions.function.ts | 33 - src/utilities/fs.utilities.ts | 2 +- src/utilities/is-version.function.ts | 16 - src/utilities/sem-ver.utilities.ts | 270 +++ src/utilities/uuid.utilities.ts | 4 +- .../route-with-version-data.model.ts | 15 + .../supported-versions-options.model.ts | 25 + src/versioning/version.model.ts | 30 + .../versioning-service.interface.ts | 39 + src/versioning/versioning-websocket.test.ts | 351 ++++ src/versioning/versioning.service.test.ts | 582 +++++++ src/versioning/versioning.service.ts | 496 ++++++ .../websocket-controller.decorator.ts | 16 +- .../decorators/websocket-route.decorator.ts | 17 +- .../base-websocket-connection.model.ts | 6 + .../socket-io-websocket-connection.model.ts | 9 +- ...et-controller-route-configuration.model.ts | 9 +- .../models/websocket-message.model.ts | 4 +- .../models/websocket-request.model.ts | 7 +- src/websocket/services/websocket.service.ts | 181 +- 120 files changed, 3743 insertions(+), 1410 deletions(-) delete mode 100644 sandbox/src/models/generated/petstore/petstore.update-pet-with-form.model.ts delete mode 100644 sandbox/src/models/generated/petstore/petstore.upload-file.model.ts create mode 100644 sandbox/versions/1.0.0.json create mode 100644 sandbox/versions/1.0.1.json create mode 100644 src/routing/decorators/http-decorator-option-input.model.ts delete mode 100644 src/types/version.type.ts delete mode 100644 src/utilities/compare-versions.function.ts delete mode 100644 src/utilities/is-version.function.ts create mode 100644 src/utilities/sem-ver.utilities.ts create mode 100644 src/versioning/route-with-version-data.model.ts create mode 100644 src/versioning/supported-versions-options.model.ts create mode 100644 src/versioning/version.model.ts create mode 100644 src/versioning/versioning-service.interface.ts create mode 100644 src/versioning/versioning-websocket.test.ts create mode 100644 src/versioning/versioning.service.test.ts create mode 100644 src/versioning/versioning.service.ts diff --git a/docs/creating-endpoints.md b/docs/creating-endpoints.md index bcd1cc1..399a615 100644 --- a/docs/creating-endpoints.md +++ b/docs/creating-endpoints.md @@ -45,7 +45,7 @@ class User { id!: string; @Property.string({ required: false }) - name?: string; + name?: string | null; @Property.string({ format: 'email' }) email!: string; @@ -107,7 +107,7 @@ class User { id!: string; @Property.string({ required: false }) - name?: string; + name?: string | null; @Property.string({ format: 'email' }) email!: string; diff --git a/docs/plugin.md b/docs/plugin.md index f87f5c1..87446e6 100644 --- a/docs/plugin.md +++ b/docs/plugin.md @@ -1 +1,2 @@ -# TODO \ No newline at end of file +# TODO +# Plugin diff --git a/package-lock.json b/package-lock.json index 3d7285a..e134faa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "zibri", - "version": "2.4.1", + "version": "2.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "zibri", - "version": "2.4.1", + "version": "2.5.0", "license": "MIT", "dependencies": { "@fastify/busboy": "^3.2.0", @@ -14,36 +14,35 @@ "express": "^5.2.1", "glob": "^13.0.6", "node-cron": "^4.2.1", - "nodemailer": "^8.0.7", - "pg": "^8.20.0", + "nodemailer": "^8.0.8", + "pg": "^8.21.0", "prom-client": "^15.1.3", "reflect-metadata": "^0.2.2", - "swagger-ui-express": "^5.0.1", "swagger2openapi": "^7.0.8", "systeminformation": "^5.31.6", "typeorm": "^0.3.29" }, "devDependencies": { "@faker-js/faker": "^9.9.0", - "@jest/globals": "^30.3.0", - "@swc/core": "^1.15.33", - "@testcontainers/postgresql": "^11.14.0", + "@jest/globals": "^30.4.1", + "@swc/core": "^1.15.40", + "@testcontainers/postgresql": "^12.0.0", "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "^25.6.0", + "@types/node": "^25.9.1", "@types/nodemailer": "^8.0.0", "@types/pdfmake": "^0.2.11", - "@types/swagger-ui-express": "^4.1.8", "@types/swagger2openapi": "^7.0.4", "eslint": "^9.36.0", "eslint-config-service-soft": "^2.1.6", - "jest": "^30.3.0", + "jest": "^30.4.2", "npm-run-all": "^4.1.5", "openapi3-ts": "^4.5.0", - "testcontainers": "^11.14.0", - "ts-jest": "^29.4.9", + "socket.io-client": "^4.8.3", + "testcontainers": "^12.0.0", + "ts-jest": "^29.4.11", "typedoc": "^0.28.19", "typescript": "^5.9.2" }, @@ -51,7 +50,7 @@ "node": ">=20" }, "peerDependencies": { - "axios": "^1.16.0", + "axios": "^1.16.1", "bcryptjs": "^3.0.3", "bignumber.js": "^11.1.1", "cookie-parser": "^1.4.7", @@ -59,13 +58,12 @@ "hi-base32": "^0.5.1", "jsonwebtoken": "^9.0.3", "otpauth": "^9.5.1", - "pdfmake": "^0.2.2", - "preact": "^10.29.1", - "preact-render-to-string": "^6.6.7", + "pdfmake": "^0.2.23", + "preact": "^10.29.2", + "preact-render-to-string": "^6.7.0", "rxjs": "^7.8.2", "socket.io": "^4.8.3", "ts-node": "^10.9.2", - "uuid": "^11.1.1", "xmlbuilder2": "^4.0.3" } }, @@ -463,9 +461,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", "dev": true, "license": "MIT", "engines": { @@ -630,13 +628,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -756,13 +754,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -1470,29 +1468,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -1771,9 +1746,9 @@ "license": "MIT" }, "node_modules/@grpc/grpc-js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1785,15 +1760,15 @@ } }, "node_modules/@grpc/grpc-js/node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", "dev": true, "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.5.3", + "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { @@ -2045,17 +2020,17 @@ } }, "node_modules/@jest/console": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", - "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", + "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -2063,38 +2038,39 @@ } }, "node_modules/@jest/core": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", - "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", + "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.3.0", - "@jest/pattern": "30.0.1", - "@jest/reporters": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/pattern": "30.4.0", + "@jest/reporters": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", + "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.3.0", - "jest-config": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-resolve-dependencies": "30.3.0", - "jest-runner": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", - "jest-watcher": "30.3.0", - "pretty-format": "30.3.0", + "jest-changed-files": "30.4.1", + "jest-config": "30.4.2", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-resolve-dependencies": "30.4.2", + "jest-runner": "30.4.2", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", + "jest-watcher": "30.4.1", + "pretty-format": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -2110,9 +2086,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", - "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", + "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", "dev": true, "license": "MIT", "engines": { @@ -2120,39 +2096,39 @@ } }, "node_modules/@jest/environment": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", - "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", + "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.3.0" + "jest-mock": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.3.0", - "jest-snapshot": "30.3.0" + "expect": "30.4.1", + "jest-snapshot": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", - "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", + "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2163,18 +2139,18 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", - "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", + "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", - "@sinonjs/fake-timers": "^15.0.0", + "@jest/types": "30.4.1", + "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -2191,47 +2167,47 @@ } }, "node_modules/@jest/globals": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", - "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", + "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/types": "30.3.0", - "jest-mock": "30.3.0" + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/types": "30.4.1", + "jest-mock": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/pattern": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", - "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", + "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-regex-util": "30.0.1" + "jest-regex-util": "30.4.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", - "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", + "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -2244,9 +2220,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -2264,9 +2240,9 @@ } }, "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -2336,9 +2312,9 @@ } }, "node_modules/@jest/schemas": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", - "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", + "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2349,13 +2325,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", - "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", + "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -2380,14 +2356,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", - "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", + "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/types": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -2396,15 +2372,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", - "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", + "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.3.0", + "@jest/test-result": "30.4.1", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", + "jest-haste-map": "30.4.1", "slash": "^3.0.0" }, "engines": { @@ -2412,23 +2388,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", - "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", + "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@jridgewell/trace-mapping": "^0.3.25", "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", + "jest-haste-map": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -2438,14 +2414,14 @@ } }, "node_modules/@jest/types": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", - "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", + "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.0.1", - "@jest/schemas": "30.0.5", + "@jest/pattern": "30.4.0", + "@jest/schemas": "30.4.1", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -2526,16 +2502,22 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { @@ -2699,9 +2681,9 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "dev": true, "license": "BSD-3-Clause" }, @@ -2757,13 +2739,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@scarf/scarf": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", - "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", - "hasInstallScript": true, - "license": "Apache-2.0" - }, "node_modules/@shikijs/engine-oniguruma": { "version": "3.23.0", "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.23.0.tgz", @@ -2814,9 +2789,9 @@ "license": "MIT" }, "node_modules/@sinclair/typebox": { - "version": "0.34.48", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.48.tgz", - "integrity": "sha512-kKJTNuK3AQOrgjjotVxMrCn1sUJwM76wMszfq1kdU4uYVJjvEWuFQ6HgvLt4Xz3fSmZlTOxJ/Ie13KnIcWQXFA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true, "license": "MIT" }, @@ -2844,9 +2819,9 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "15.3.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.2.tgz", - "integrity": "sha512-mrn35Jl2pCpns+mE3HaZa1yPN5EYCRgiMI+135COjr2hr8Cls9DXqIZ57vZe2cz7y2XVSq92tcs6kGQcT1J8Rw==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.4.0.tgz", + "integrity": "sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2887,9 +2862,9 @@ } }, "node_modules/@swc/core": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.33.tgz", - "integrity": "sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.40.tgz", + "integrity": "sha512-2kwzJikRvgtNAG7MwVZY2vEzZjTxKIq5jXOihuSV/8U+Hej8Va22t65aKnJZs3P+NwojZvR8Mf8kyM7O+V8sQg==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -2906,18 +2881,18 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.33", - "@swc/core-darwin-x64": "1.15.33", - "@swc/core-linux-arm-gnueabihf": "1.15.33", - "@swc/core-linux-arm64-gnu": "1.15.33", - "@swc/core-linux-arm64-musl": "1.15.33", - "@swc/core-linux-ppc64-gnu": "1.15.33", - "@swc/core-linux-s390x-gnu": "1.15.33", - "@swc/core-linux-x64-gnu": "1.15.33", - "@swc/core-linux-x64-musl": "1.15.33", - "@swc/core-win32-arm64-msvc": "1.15.33", - "@swc/core-win32-ia32-msvc": "1.15.33", - "@swc/core-win32-x64-msvc": "1.15.33" + "@swc/core-darwin-arm64": "1.15.40", + "@swc/core-darwin-x64": "1.15.40", + "@swc/core-linux-arm-gnueabihf": "1.15.40", + "@swc/core-linux-arm64-gnu": "1.15.40", + "@swc/core-linux-arm64-musl": "1.15.40", + "@swc/core-linux-ppc64-gnu": "1.15.40", + "@swc/core-linux-s390x-gnu": "1.15.40", + "@swc/core-linux-x64-gnu": "1.15.40", + "@swc/core-linux-x64-musl": "1.15.40", + "@swc/core-win32-arm64-msvc": "1.15.40", + "@swc/core-win32-ia32-msvc": "1.15.40", + "@swc/core-win32-x64-msvc": "1.15.40" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -2929,9 +2904,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.33.tgz", - "integrity": "sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.40.tgz", + "integrity": "sha512-PaYyclfmQ++77D8ityYvmmVzHv9aG8ROwt2GfG6/ccloy4Hgf80qtOnzb9VYvPsUT7Ty1uhuDRhv3XYpf62qhQ==", "cpu": [ "arm64" ], @@ -2945,9 +2920,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.33.tgz", - "integrity": "sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.40.tgz", + "integrity": "sha512-HbbPzvfLBUXjIB1Ezks+//lNUjmLjfyd63XSwprJgrZaXYdm70kohXPJUWdqKZozolFxbPaO+xtBaiUp6BoueA==", "cpu": [ "x64" ], @@ -2961,9 +2936,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.33.tgz", - "integrity": "sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.40.tgz", + "integrity": "sha512-SlRZsCjOCPR2LvFs0Ri/Xrx/5o5TCt8vl4gW6mX1hEZOG0a625RxzRHpHdAQNGykmAN/7IeaFAJG+QnNmxlHcA==", "cpu": [ "arm" ], @@ -2977,9 +2952,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.33.tgz", - "integrity": "sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.40.tgz", + "integrity": "sha512-Q8byxJt2fh8CR3EUX6snBpy47AoBVm+In/+Z3rjDHMjC38ZvR9/gtUUNCT0tfrn4EdVsO8/QPi59nxrxvqxvBQ==", "cpu": [ "arm64" ], @@ -2993,9 +2968,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.33.tgz", - "integrity": "sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.40.tgz", + "integrity": "sha512-4z0MgHU+7M0pZDqBN1El7mFXDI1SBwinfcUkAyA4v8QrhOIUOZltySt2aStQLZGrdXVXM4Y4ylfiTC04ED+MoQ==", "cpu": [ "arm64" ], @@ -3009,9 +2984,9 @@ } }, "node_modules/@swc/core-linux-ppc64-gnu": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.33.tgz", - "integrity": "sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.40.tgz", + "integrity": "sha512-fLI4iUgeSZu0eRWUXwe6YzPFx9gHbFiPkl8Rp3mJfP8OpNR3nTQCGPvHdDh9xniW7mVvgMY4ni7A4VzqI1KrpA==", "cpu": [ "ppc64" ], @@ -3025,9 +3000,9 @@ } }, "node_modules/@swc/core-linux-s390x-gnu": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.33.tgz", - "integrity": "sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.40.tgz", + "integrity": "sha512-YqeKMAb7d4nQSGMJQ454IlaCENpzcDqhvBE9+CPfdnYpnUXxd+BSrB6Xk0YjW8UyoEhUj4p6quATCxbsp6J3jg==", "cpu": [ "s390x" ], @@ -3041,9 +3016,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.33.tgz", - "integrity": "sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.40.tgz", + "integrity": "sha512-7HOuS1iGcme/j/TuL1TfmmLGiMQrjv/GmjyZeydl00FKPtpGXEldwqfI56xgd1YzrzoB2svWjxbGGyQ0TEASxg==", "cpu": [ "x64" ], @@ -3057,9 +3032,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.33.tgz", - "integrity": "sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.40.tgz", + "integrity": "sha512-h4kZYHc7dpc9P9u4brRJaS8Pl7tPVHAeiLSzw7T5RfIJgAoSdaCMKzI/2Uay9gFhaw8uyCDl0L5q37r0EpAfIA==", "cpu": [ "x64" ], @@ -3073,9 +3048,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.33.tgz", - "integrity": "sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.40.tgz", + "integrity": "sha512-+mQgKZXSj6mV38Zh05QaxSjUDmGP/R2JWlXZTDLSPkDzHU6p3GxN9eeSf5dfyDVU86946fmCvSzyl/ucImx8+A==", "cpu": [ "arm64" ], @@ -3089,9 +3064,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.33.tgz", - "integrity": "sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.40.tgz", + "integrity": "sha512-yvwdPLGd25mcj/mNatjNQ0lZujtQD6psH3v9PNmMb+fSzjbNG8KIDxjFWrcV+fsFVLOkyOmdJsFmX7NAFjVyPw==", "cpu": [ "ia32" ], @@ -3105,9 +3080,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.33", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.33.tgz", - "integrity": "sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==", + "version": "1.15.40", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.40.tgz", + "integrity": "sha512-OXtKsLU1bVtInzzDEAY2sYiF/rl4tvAnLLLpuMp3HzAOQZ5A+i69AKDhA1YLQTaMAqO3vzyYNVAYVRMPtSYD4w==", "cpu": [ "x64" ], @@ -3138,13 +3113,13 @@ } }, "node_modules/@testcontainers/postgresql": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-11.14.0.tgz", - "integrity": "sha512-wYbJn8GRTj8qfqzfVubxioYWlHJU/ImIjuzPwyy9C5Qfo6g3GLduPZAj+BifvqTZjgT3gd4gFVLCPhBji7dc1w==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@testcontainers/postgresql/-/postgresql-12.0.0.tgz", + "integrity": "sha512-mqGQHwmY+xLKFvFd3XQYaa0vDJRaJAOUfFWYbgjd4wb6hOlrK7xhszaXB7KuGCGTIJf5jvtoEB8/56oVB5s55w==", "dev": true, "license": "MIT", "dependencies": { - "testcontainers": "^11.14.0" + "testcontainers": "^12.0.0" } }, "node_modules/@tsconfig/node10": { @@ -3172,9 +3147,9 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -3407,13 +3382,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~7.19.0" + "undici-types": ">=7.24.0 <7.24.7" } }, "node_modules/@types/nodemailer": { @@ -3526,17 +3501,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/swagger-ui-express": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", - "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*", - "@types/serve-static": "*" - } - }, "node_modules/@types/swagger2openapi": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/swagger2openapi/-/swagger2openapi-7.0.4.tgz", @@ -3555,6 +3519,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -3773,9 +3746,9 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -3865,9 +3838,9 @@ "license": "ISC" }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", - "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", "cpu": [ "arm" ], @@ -3879,9 +3852,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", - "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", "cpu": [ "arm64" ], @@ -3893,9 +3866,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", - "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", "cpu": [ "arm64" ], @@ -3907,9 +3880,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", - "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", "cpu": [ "x64" ], @@ -3921,9 +3894,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", - "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", "cpu": [ "x64" ], @@ -3935,9 +3908,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", - "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", "cpu": [ "arm" ], @@ -3949,9 +3922,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", - "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", "cpu": [ "arm" ], @@ -3963,9 +3936,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", - "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", "cpu": [ "arm64" ], @@ -3977,9 +3950,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", - "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", "cpu": [ "arm64" ], @@ -3990,10 +3963,38 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", - "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", "cpu": [ "ppc64" ], @@ -4005,9 +4006,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", - "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", "cpu": [ "riscv64" ], @@ -4019,9 +4020,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", - "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", "cpu": [ "riscv64" ], @@ -4033,9 +4034,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", - "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", "cpu": [ "s390x" ], @@ -4047,9 +4048,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", - "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", "cpu": [ "x64" ], @@ -4061,9 +4062,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", - "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", "cpu": [ "x64" ], @@ -4074,10 +4075,24 @@ "linux" ] }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", - "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", "cpu": [ "wasm32" ], @@ -4085,16 +4100,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.11" + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", - "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", "cpu": [ "arm64" ], @@ -4106,9 +4123,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", - "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", "cpu": [ "ia32" ], @@ -4120,9 +4137,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", - "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", "cpu": [ "x64" ], @@ -4194,6 +4211,18 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", @@ -4380,9 +4409,9 @@ } }, "node_modules/archiver-utils/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -4685,14 +4714,15 @@ } }, "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", "license": "MIT", "peer": true, "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, @@ -4707,9 +4737,9 @@ } }, "node_modules/b4a": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", - "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -4722,16 +4752,16 @@ } }, "node_modules/babel-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", - "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", + "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.3.0", + "@jest/transform": "30.4.1", "@types/babel__core": "^7.20.5", "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.3.0", + "babel-preset-jest": "30.4.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -4764,9 +4794,9 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", - "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", + "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", "dev": true, "license": "MIT", "dependencies": { @@ -4804,13 +4834,13 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", - "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", + "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.3.0", + "babel-plugin-jest-hoist": "30.4.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { @@ -4827,9 +4857,9 @@ "license": "MIT" }, "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.3.tgz", + "integrity": "sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==", "dev": true, "license": "Apache-2.0", "peerDependencies": { @@ -4842,9 +4872,9 @@ } }, "node_modules/bare-fs": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.0.tgz", - "integrity": "sha512-xzqKsCFxAek9aezYhjJuJRXBIaYlg/0OGDTZp+T8eYmYMlm66cs6cYko02drIyjN2CBbi+I6L7YfXyqpqtKRXA==", + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", + "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4867,9 +4897,9 @@ } }, "node_modules/bare-os": { - "version": "3.8.7", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.8.7.tgz", - "integrity": "sha512-G4Gr1UsGeEy2qtDTZwL7JFLo2wapUarz7iTMcYcMFdS89AIQuBoyjgXZz0Utv7uHs3xA9LckhVbeBi8lEQrC+w==", + "version": "3.9.1", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz", + "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -4887,9 +4917,9 @@ } }, "node_modules/bare-stream": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.0.tgz", - "integrity": "sha512-3zAJRZMDFGjdn+RVnNpF9kuELw+0Fl3lpndM4NcEOhb9zwtSo/deETfuIwMSE5BXanA0FrN1qVjffGwAg2Y7EA==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", + "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4914,9 +4944,9 @@ } }, "node_modules/bare-url": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.0.tgz", - "integrity": "sha512-NSTU5WN+fy/L0DDenfE8SXQna4voXuW0FHM7wH8i3/q9khUSchfPbPezO4zSFMnDGIf9YE+mt/RWhZgNRKRIXA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.3.tgz", + "integrity": "sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6177,9 +6207,9 @@ } }, "node_modules/dockerode": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.10.tgz", - "integrity": "sha512-8L/P9JynLBiG7/coiA4FlQXegHltRqS0a+KqI44P1zgQh8QLHTg7FKOwhkBgSJwZTeHsq30WRoVFLuwkfK0YFg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-5.0.0.tgz", + "integrity": "sha512-C52mvJ+7lcyhWNfrzVfFsbTrBfy/ezE9FGEYLpu17FUeBcCkxERk9nN7uDl/478ynDiQ4U+5DbQC2vENHkVEtQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -6188,11 +6218,10 @@ "@grpc/proto-loader": "^0.7.13", "docker-modem": "^5.0.7", "protobufjs": "^7.3.2", - "tar-fs": "^2.1.4", - "uuid": "^10.0.0" + "tar-fs": "^2.1.4" }, "engines": { - "node": ">= 8.0" + "node": ">= 14.17" } }, "node_modules/dockerode/node_modules/readable-stream": { @@ -6240,20 +6269,6 @@ "node": ">=6" } }, - "node_modules/dockerode/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -6360,25 +6375,40 @@ } }, "node_modules/engine.io": { - "version": "6.6.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", - "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "version": "6.6.8", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz", + "integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==", "license": "MIT", "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.18.3" + "ws": "~8.20.1" }, "engines": { "node": ">=10.2.0" } }, + "node_modules/engine.io-client": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.5.tgz", + "integrity": "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.20.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, "node_modules/engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", @@ -7732,9 +7762,9 @@ } }, "node_modules/eslint-plugin-sonarjs/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -8044,18 +8074,18 @@ } }, "node_modules/expect": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", - "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", + "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.3.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-util": "30.3.0" + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -8066,7 +8096,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -8674,9 +8703,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -8964,6 +8993,19 @@ "integrity": "sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA==", "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -9772,17 +9814,17 @@ } }, "node_modules/jest": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", - "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", + "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@jest/core": "30.3.0", - "@jest/types": "30.3.0", + "@jest/core": "30.4.2", + "@jest/types": "30.4.1", "import-local": "^3.2.0", - "jest-cli": "30.3.0" + "jest-cli": "30.4.2" }, "bin": { "jest": "bin/jest.js" @@ -9800,14 +9842,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", - "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", + "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "p-limit": "^3.1.0" }, "engines": { @@ -9815,29 +9857,29 @@ } }, "node_modules/jest-circus": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", - "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", + "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/expect": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/expect": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-runtime": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", + "jest-each": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-runtime": "30.4.2", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "p-limit": "^3.1.0", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -9847,21 +9889,21 @@ } }, "node_modules/jest-cli": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", - "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", + "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/core": "30.4.2", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-config": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "yargs": "^17.7.2" }, "bin": { @@ -9880,33 +9922,33 @@ } }, "node_modules/jest-config": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", - "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", + "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", - "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.3.0", - "@jest/types": "30.3.0", - "babel-jest": "30.3.0", + "@jest/pattern": "30.4.0", + "@jest/test-sequencer": "30.4.1", + "@jest/types": "30.4.1", + "babel-jest": "30.4.1", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-circus": "30.3.0", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-runner": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-circus": "30.4.2", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-runner": "30.4.2", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "parse-json": "^5.2.0", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -9931,9 +9973,9 @@ } }, "node_modules/jest-config/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -10003,25 +10045,25 @@ } }, "node_modules/jest-diff": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", - "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", + "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.3.0", + "@jest/diff-sequences": "30.4.0", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", - "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", + "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", "dev": true, "license": "MIT", "dependencies": { @@ -10032,56 +10074,56 @@ } }, "node_modules/jest-each": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", - "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", + "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "chalk": "^4.1.2", - "jest-util": "30.3.0", - "pretty-format": "30.3.0" + "jest-util": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", - "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", + "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/types": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-mock": "30.3.0", - "jest-util": "30.3.0", - "jest-validate": "30.3.0" + "jest-mock": "30.4.1", + "jest-util": "30.4.1", + "jest-validate": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", - "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", + "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.0.1", - "jest-util": "30.3.0", - "jest-worker": "30.3.0", + "jest-regex-util": "30.4.0", + "jest-util": "30.4.1", + "jest-worker": "30.4.1", "picomatch": "^4.0.3", "walker": "^1.0.8" }, @@ -10093,49 +10135,50 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", - "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", + "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", - "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", + "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.3.0", - "pretty-format": "30.3.0" + "jest-diff": "30.4.1", + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", - "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", + "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", + "jest-util": "30.4.1", "picomatch": "^4.0.3", - "pretty-format": "30.3.0", + "pretty-format": "30.4.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -10144,15 +10187,15 @@ } }, "node_modules/jest-mock": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", - "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", + "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", - "jest-util": "30.3.0" + "jest-util": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -10177,9 +10220,9 @@ } }, "node_modules/jest-regex-util": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", - "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "version": "30.4.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", + "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", "dev": true, "license": "MIT", "engines": { @@ -10187,18 +10230,18 @@ } }, "node_modules/jest-resolve": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", - "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", + "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", + "jest-haste-map": "30.4.1", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.3.0", - "jest-validate": "30.3.0", + "jest-util": "30.4.1", + "jest-validate": "30.4.1", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -10207,46 +10250,46 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", - "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", + "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.0.1", - "jest-snapshot": "30.3.0" + "jest-regex-util": "30.4.0", + "jest-snapshot": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", - "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", + "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.3.0", - "@jest/environment": "30.3.0", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/console": "30.4.1", + "@jest/environment": "30.4.1", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.2.0", - "jest-environment-node": "30.3.0", - "jest-haste-map": "30.3.0", - "jest-leak-detector": "30.3.0", - "jest-message-util": "30.3.0", - "jest-resolve": "30.3.0", - "jest-runtime": "30.3.0", - "jest-util": "30.3.0", - "jest-watcher": "30.3.0", - "jest-worker": "30.3.0", + "jest-docblock": "30.4.0", + "jest-environment-node": "30.4.1", + "jest-haste-map": "30.4.1", + "jest-leak-detector": "30.4.1", + "jest-message-util": "30.4.1", + "jest-resolve": "30.4.1", + "jest-runtime": "30.4.2", + "jest-util": "30.4.1", + "jest-watcher": "30.4.1", + "jest-worker": "30.4.1", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -10255,32 +10298,32 @@ } }, "node_modules/jest-runtime": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", - "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", + "version": "30.4.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", + "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.3.0", - "@jest/fake-timers": "30.3.0", - "@jest/globals": "30.3.0", + "@jest/environment": "30.4.1", + "@jest/fake-timers": "30.4.1", + "@jest/globals": "30.4.1", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/test-result": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.5.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.3.0", - "jest-message-util": "30.3.0", - "jest-mock": "30.3.0", - "jest-regex-util": "30.0.1", - "jest-resolve": "30.3.0", - "jest-snapshot": "30.3.0", - "jest-util": "30.3.0", + "jest-haste-map": "30.4.1", + "jest-message-util": "30.4.1", + "jest-mock": "30.4.1", + "jest-regex-util": "30.4.0", + "jest-resolve": "30.4.1", + "jest-snapshot": "30.4.1", + "jest-util": "30.4.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -10289,9 +10332,9 @@ } }, "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -10361,9 +10404,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", - "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", + "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", "dev": true, "license": "MIT", "dependencies": { @@ -10372,20 +10415,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.3.0", + "@jest/expect-utils": "30.4.1", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.3.0", - "@jest/transform": "30.3.0", - "@jest/types": "30.3.0", + "@jest/snapshot-utils": "30.4.1", + "@jest/transform": "30.4.1", + "@jest/types": "30.4.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.3.0", + "expect": "30.4.1", "graceful-fs": "^4.2.11", - "jest-diff": "30.3.0", - "jest-matcher-utils": "30.3.0", - "jest-message-util": "30.3.0", - "jest-util": "30.3.0", - "pretty-format": "30.3.0", + "jest-diff": "30.4.1", + "jest-matcher-utils": "30.4.1", + "jest-message-util": "30.4.1", + "jest-util": "30.4.1", + "pretty-format": "30.4.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -10394,13 +10437,13 @@ } }, "node_modules/jest-util": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", - "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", + "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -10412,18 +10455,18 @@ } }, "node_modules/jest-validate": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", - "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", + "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", "dev": true, "license": "MIT", "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.3.0", + "@jest/types": "30.4.1", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.3.0" + "pretty-format": "30.4.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -10443,19 +10486,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", - "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", + "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.3.0", - "@jest/types": "30.3.0", + "@jest/test-result": "30.4.1", + "@jest/types": "30.4.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "string-length": "^4.0.2" }, "engines": { @@ -10463,15 +10506,15 @@ } }, "node_modules/jest-worker": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", - "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", + "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.3.0", + "jest-util": "30.4.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -11261,9 +11304,9 @@ "license": "MIT" }, "node_modules/nan": { - "version": "2.26.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz", - "integrity": "sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==", + "version": "2.27.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.27.0.tgz", + "integrity": "sha512-hC+0LidcL3XE4rp1C4H54KujgXKzbfyTngZTwBByQxsOxCEKZT0MPQ4hOKUH2jU1OYstqdDH4onyHPDzcV0XdQ==", "dev": true, "license": "MIT", "optional": true @@ -11407,9 +11450,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.7.tgz", - "integrity": "sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.8.tgz", + "integrity": "sha512-p+XsnzXGdtIHXUu2ugxdfG+eX2nehsGhMjW9h0CWj1BhE30hrFz0kh0yIM0/VjUgVsRrDj+80ZO+I1nSkGE4tA==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -12231,15 +12274,15 @@ } }, "node_modules/pg": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz", - "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", + "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", "license": "MIT", "peer": true, "dependencies": { - "pg-connection-string": "^2.12.0", - "pg-pool": "^3.13.0", - "pg-protocol": "^1.13.0", + "pg-connection-string": "^2.13.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.14.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -12247,7 +12290,7 @@ "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.3.0" + "pg-cloudflare": "^1.4.0" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -12259,16 +12302,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz", - "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz", - "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.13.0.tgz", + "integrity": "sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==", "license": "MIT" }, "node_modules/pg-int8": { @@ -12281,18 +12324,18 @@ } }, "node_modules/pg-pool": { - "version": "3.13.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz", - "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==", + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz", - "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==", + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.14.0.tgz", + "integrity": "sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==", "license": "MIT" }, "node_modules/pg-types": { @@ -12506,9 +12549,9 @@ } }, "node_modules/preact": { - "version": "10.29.1", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.1.tgz", - "integrity": "sha512-gQCLc/vWroE8lIpleXtdJhTFDogTdZG9AjMUpVkDf2iTCNwYNWA+u16dL41TqUDJO4gm2IgrcMv3uTpjd4Pwmg==", + "version": "10.29.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.2.tgz", + "integrity": "sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==", "license": "MIT", "peer": true, "funding": { @@ -12517,9 +12560,9 @@ } }, "node_modules/preact-render-to-string": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.6.7.tgz", - "integrity": "sha512-3XdbsX3+vn9dQW+jJI/FsI9rlkgl6dbeUpqLsChak6jp3j3auFqBCkno7VChbMFs5Q8ylBj6DrUkKRwtVN3nvw==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.7.0.tgz", + "integrity": "sha512-Z4WR8fmLMRpdYqJ9i7vrlXSsSrxVJydwrkEXHapexfARbWfGb7vGcnvNQnIzN0cXciMVOlz/XLoiMCi9gUsy9Q==", "license": "MIT", "peer": true, "peerDependencies": { @@ -12567,15 +12610,16 @@ } }, "node_modules/pretty-format": { - "version": "30.3.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", - "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", + "version": "30.4.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", + "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.0.5", + "@jest/schemas": "30.4.1", "ansi-styles": "^5.2.0", - "react-is": "^18.3.1" + "react-is-18": "npm:react-is@^18.3.1", + "react-is-19": "npm:react-is@^19.2.5" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -12655,9 +12699,9 @@ } }, "node_modules/protobufjs": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.9.tgz", - "integrity": "sha512-Od4muIm3HW1AouyHF5lONOf1FWo3hY1NbFDoy191X9GzhpgW1clCoaFjfVs2rKJNFYpTNJbje4cbAIDBZJ63ZA==", + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.1.tgz", + "integrity": "sha512-4K0myLaWL5EteuSAro91EGFgcfVgxb64Jx+7oDAY6GOkXD4M69yuSEljNcInGVCA5sOPxmZ/EqDLj2x0Q0+Ygg==", "dev": true, "hasInstallScript": true, "license": "BSD-3-Clause", @@ -12665,7 +12709,7 @@ "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", - "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.2", @@ -12673,7 +12717,7 @@ "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" @@ -12750,9 +12794,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -12788,13 +12832,22 @@ "node": ">= 0.10" } }, - "node_modules/react-is": { + "node_modules/react-is-18": { + "name": "react-is", "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, "license": "MIT" }, + "node_modules/react-is-19": { + "name": "react-is", + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.6.tgz", + "integrity": "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==", + "dev": true, + "license": "MIT" + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -12838,9 +12891,9 @@ } }, "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", - "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", "dev": true, "license": "MIT", "dependencies": { @@ -13570,13 +13623,29 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", - "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz", + "integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==", "license": "MIT", "dependencies": { "debug": "~4.4.1", - "ws": "~8.18.3" + "ws": "~8.20.1" + } + }, + "node_modules/socket.io-client": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", + "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" } }, "node_modules/socket.io-parser": { @@ -14190,30 +14259,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/swagger-ui-dist": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", - "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", - "license": "Apache-2.0", - "dependencies": { - "@scarf/scarf": "=1.4.0" - } - }, - "node_modules/swagger-ui-express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", - "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", - "license": "MIT", - "dependencies": { - "swagger-ui-dist": ">=5.0.0" - }, - "engines": { - "node": ">= v0.10.32" - }, - "peerDependencies": { - "express": ">=4.0.0 || >=5.0.0-beta" - } - }, "node_modules/swagger2openapi": { "version": "7.0.8", "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-7.0.8.tgz", @@ -14308,9 +14353,9 @@ } }, "node_modules/tar-stream": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.8.tgz", - "integrity": "sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz", + "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==", "dev": true, "license": "MIT", "dependencies": { @@ -14377,9 +14422,9 @@ } }, "node_modules/testcontainers": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-11.14.0.tgz", - "integrity": "sha512-r9pniwv/iwzyHaI7gwAvAm4Y+IvjJg3vBWdjrUCaDMc2AXIr4jKbq7jJO18Mw2ybs73pZy1Aj7p/4RVBGMRWjg==", + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/testcontainers/-/testcontainers-12.0.0.tgz", + "integrity": "sha512-/PdRvFvuHPwX126HR7RO0cEgLD3Nr8sWZyWSv54ei92TT79BubUkOCU5uwTc8ufTsTGQf0v6nyvZJVVVyR9Uqw==", "dev": true, "license": "MIT", "dependencies": { @@ -14390,14 +14435,14 @@ "byline": "^5.0.0", "debug": "^4.4.3", "docker-compose": "^1.4.2", - "dockerode": "^4.0.10", + "dockerode": "^5.0.0", "get-port": "^7.2.0", "proper-lockfile": "^4.1.2", "properties-reader": "^3.0.1", "ssh-remote-port-forward": "^1.0.4", "tar-fs": "^3.1.2", "tmp": "^0.2.5", - "undici": "^7.24.5" + "undici": "^7.24.7" } }, "node_modules/text-decoder": { @@ -14434,9 +14479,9 @@ } }, "node_modules/tmp": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", - "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.6.tgz", + "integrity": "sha512-5sJPdPjfI5Kx+qbrDesxkglRBxW//g7hCsqspEjwkewGvBMGIKMOTKzLt1hFVJzyadba3lDUN20O9qhvbQUSTA==", "dev": true, "license": "MIT", "engines": { @@ -14510,9 +14555,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.9", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", - "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", + "version": "29.4.11", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", + "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", "dev": true, "license": "MIT", "dependencies": { @@ -14522,7 +14567,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.4", + "semver": "^7.8.0", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -14563,9 +14608,9 @@ } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "dev": true, "license": "ISC", "bin": { @@ -14843,9 +14888,9 @@ } }, "node_modules/typedoc/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { @@ -15118,9 +15163,9 @@ } }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.26.0.tgz", + "integrity": "sha512-3O9Tf67pGhgOv9jM35AbhkXAKi13f3oy3aE4CSgr+TckGeY+/iu97ZXN+J7DpHPzLbVApFd1IFhcnBjREYXYcg==", "dev": true, "license": "MIT", "engines": { @@ -15128,9 +15173,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", "license": "MIT" }, "node_modules/unicode-properties": { @@ -15163,38 +15208,41 @@ } }, "node_modules/unrs-resolver": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", - "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.3.0" + "napi-postinstall": "^0.3.4" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.11.1", - "@unrs/resolver-binding-android-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-arm64": "1.11.1", - "@unrs/resolver-binding-darwin-x64": "1.11.1", - "@unrs/resolver-binding-freebsd-x64": "1.11.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", - "@unrs/resolver-binding-linux-x64-musl": "1.11.1", - "@unrs/resolver-binding-wasm32-wasi": "1.11.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" } }, "node_modules/update-browserslist-db": { @@ -15254,7 +15302,6 @@ "https://github.com/sponsors/ctavan" ], "license": "MIT", - "peer": true, "bin": { "uuid": "dist/esm/bin/uuid" } @@ -15621,9 +15668,9 @@ } }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -15682,6 +15729,15 @@ "node": ">=12.0.0" } }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index aeb0bc7..539231f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zibri", - "version": "2.4.1", + "version": "2.5.0", "main": "./dist/cjs/index.js", "types": "./dist/cjs/index.d.ts", "module": "./dist/esm/index.mjs", @@ -49,22 +49,21 @@ "license": "MIT", "description": "TS Backend Framework", "peerDependencies": { - "axios": "^1.16.0", + "axios": "^1.16.1", "bcryptjs": "^3.0.3", "bignumber.js": "^11.1.1", + "cookie-parser": "^1.4.7", "handlebars": "^4.7.9", "hi-base32": "^0.5.1", "jsonwebtoken": "^9.0.3", "otpauth": "^9.5.1", - "pdfmake": "^0.2.2", - "preact": "^10.29.1", - "preact-render-to-string": "^6.6.7", + "pdfmake": "^0.2.23", + "preact": "^10.29.2", + "preact-render-to-string": "^6.7.0", "rxjs": "^7.8.2", "socket.io": "^4.8.3", "ts-node": "^10.9.2", - "uuid": "^11.1.1", - "xmlbuilder2": "^4.0.3", - "cookie-parser": "^1.4.7" + "xmlbuilder2": "^4.0.3" }, "dependencies": { "@fastify/busboy": "^3.2.0", @@ -72,36 +71,35 @@ "express": "^5.2.1", "glob": "^13.0.6", "node-cron": "^4.2.1", - "nodemailer": "^8.0.7", - "pg": "^8.20.0", + "nodemailer": "^8.0.8", + "pg": "^8.21.0", "prom-client": "^15.1.3", "reflect-metadata": "^0.2.2", - "swagger-ui-express": "^5.0.1", "swagger2openapi": "^7.0.8", "systeminformation": "^5.31.6", "typeorm": "^0.3.29" }, "devDependencies": { "@faker-js/faker": "^9.9.0", - "@jest/globals": "^30.3.0", - "@swc/core": "^1.15.33", - "@testcontainers/postgresql": "^11.14.0", + "@jest/globals": "^30.4.1", + "@swc/core": "^1.15.40", + "@testcontainers/postgresql": "^12.0.0", "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", "@types/express": "^5.0.6", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "^25.6.0", + "@types/node": "^25.9.1", "@types/nodemailer": "^8.0.0", "@types/pdfmake": "^0.2.11", - "@types/swagger-ui-express": "^4.1.8", "@types/swagger2openapi": "^7.0.4", "eslint": "^9.36.0", "eslint-config-service-soft": "^2.1.6", - "jest": "^30.3.0", + "jest": "^30.4.2", "npm-run-all": "^4.1.5", "openapi3-ts": "^4.5.0", - "testcontainers": "^11.14.0", - "ts-jest": "^29.4.9", + "socket.io-client": "^4.8.3", + "testcontainers": "^12.0.0", + "ts-jest": "^29.4.11", "typedoc": "^0.28.19", "typescript": "^5.9.2" } diff --git a/sandbox/assets/public/open-api/custom.css b/sandbox/assets/public/open-api/custom.css index b2960bf..46c188b 100644 --- a/sandbox/assets/public/open-api/custom.css +++ b/sandbox/assets/public/open-api/custom.css @@ -1,3 +1,8 @@ +body { + margin: 0; + padding-bottom: 20px; +} + #zibri-openapi-logo { display: flex; gap: 20px; @@ -44,10 +49,19 @@ svg { filter: brightness(0) saturate(100%) invert(96%) sepia(0%) saturate(0%) hue-rotate(0deg) contrast(1); } +.swagger-ui .topbar svg { + /* filter: brightness(0) saturate(100%) invert(96%) sepia(0%) saturate(0%) hue-rotate(0deg) contrast(1); */ + display: none; +} + .swagger-ui .info .title { text-align: center; } +.swagger-ui .info .link { + display: none; +} + .swagger-ui .json-schema-2020-12-property .json-schema-2020-12__title, .swagger-ui .tab li, .swagger-ui .parameter__type, @@ -87,6 +101,7 @@ svg { } .swagger-ui .opblock .opblock-section-header, +.swagger-ui .topbar, .swagger-ui .scheme-container { background-color: #1a1a26; } @@ -96,8 +111,8 @@ svg { padding-right: 2em; } -.swagger-ui .topbar { - display: none; +.swagger-ui .topbar .download-url-wrapper { + display: flex; } .swagger-ui .json-schema-2020-12-body { @@ -106,4 +121,11 @@ svg { .swagger-ui .json-schema-2020-12__attribute--primary { color: #019fbe; +} + +.swagger-ui .dialog-ux .auth-btn-wrapper { + justify-content: start; +} +.swagger-ui .dialog-ux .auth-btn-wrapper .btn-done { + display: none; } \ No newline at end of file diff --git a/sandbox/assets/public/open-api/swagger-ui.css b/sandbox/assets/public/open-api/swagger-ui.css index 13d49a2..f63798b 100644 --- a/sandbox/assets/public/open-api/swagger-ui.css +++ b/sandbox/assets/public/open-api/swagger-ui.css @@ -1,3 +1 @@ -.swagger-ui{color:#3b4151;font-family:sans-serif}.swagger-ui html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{background-color:transparent;-webkit-text-decoration-skip:objects}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) repeat 0 0}.swagger-ui .debug-grid-8-solid{background:#fff url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) repeat 0 0}.swagger-ui .debug-grid-16-solid{background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) repeat 0 0}.swagger-ui .border-box,.swagger-ui a,.swagger-ui article,.swagger-ui body,.swagger-ui code,.swagger-ui dd,.swagger-ui div,.swagger-ui dl,.swagger-ui dt,.swagger-ui fieldset,.swagger-ui footer,.swagger-ui form,.swagger-ui h1,.swagger-ui h2,.swagger-ui h3,.swagger-ui h4,.swagger-ui h5,.swagger-ui h6,.swagger-ui header,.swagger-ui html,.swagger-ui input[type=email],.swagger-ui input[type=number],.swagger-ui input[type=password],.swagger-ui input[type=tel],.swagger-ui input[type=text],.swagger-ui input[type=url],.swagger-ui legend,.swagger-ui li,.swagger-ui main,.swagger-ui ol,.swagger-ui p,.swagger-ui pre,.swagger-ui section,.swagger-ui table,.swagger-ui td,.swagger-ui textarea,.swagger-ui th,.swagger-ui tr,.swagger-ui ul{box-sizing:border-box}.swagger-ui .aspect-ratio{height:0;position:relative}.swagger-ui .aspect-ratio--16x9{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1{padding-bottom:100%}.swagger-ui .aspect-ratio--object{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}@media screen and (min-width:30em){.swagger-ui .aspect-ratio-ns{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-ns{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-ns{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-ns{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-ns{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-ns{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-ns{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-ns{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-ns{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-ns{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-ns{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-ns{padding-bottom:100%}.swagger-ui .aspect-ratio--object-ns{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .aspect-ratio-m{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-m{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-m{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-m{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-m{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-m{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-m{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-m{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-m{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-m{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-m{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-m{padding-bottom:100%}.swagger-ui .aspect-ratio--object-m{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}@media screen and (min-width:60em){.swagger-ui .aspect-ratio-l{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-l{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-l{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-l{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-l{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-l{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-l{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-l{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-l{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-l{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-l{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-l{padding-bottom:100%}.swagger-ui .aspect-ratio--object-l{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}.swagger-ui img{max-width:100%}.swagger-ui .cover{background-size:cover!important}.swagger-ui .contain{background-size:contain!important}@media screen and (min-width:30em){.swagger-ui .cover-ns{background-size:cover!important}.swagger-ui .contain-ns{background-size:contain!important}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .cover-m{background-size:cover!important}.swagger-ui .contain-m{background-size:contain!important}}@media screen and (min-width:60em){.swagger-ui .cover-l{background-size:cover!important}.swagger-ui .contain-l{background-size:contain!important}}.swagger-ui .bg-center{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left{background-position:0;background-repeat:no-repeat}@media screen and (min-width:30em){.swagger-ui .bg-center-ns{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-ns{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-ns{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-ns{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-ns{background-position:0;background-repeat:no-repeat}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .bg-center-m{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-m{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-m{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-m{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-m{background-position:0;background-repeat:no-repeat}}@media screen and (min-width:60em){.swagger-ui .bg-center-l{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-l{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-l{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-l{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-l{background-position:0;background-repeat:no-repeat}}.swagger-ui .outline{outline:1px solid}.swagger-ui .outline-transparent{outline:1px solid transparent}.swagger-ui .outline-0{outline:0}@media screen and (min-width:30em){.swagger-ui .outline-ns{outline:1px solid}.swagger-ui .outline-transparent-ns{outline:1px solid transparent}.swagger-ui .outline-0-ns{outline:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .outline-m{outline:1px solid}.swagger-ui .outline-transparent-m{outline:1px solid transparent}.swagger-ui .outline-0-m{outline:0}}@media screen and (min-width:60em){.swagger-ui .outline-l{outline:1px solid}.swagger-ui .outline-transparent-l{outline:1px solid transparent}.swagger-ui .outline-0-l{outline:0}}.swagger-ui .ba{border-style:solid;border-width:1px}.swagger-ui .bt{border-top-style:solid;border-top-width:1px}.swagger-ui .br{border-right-style:solid;border-right-width:1px}.swagger-ui .bb{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl{border-left-style:solid;border-left-width:1px}.swagger-ui .bn{border-style:none;border-width:0}@media screen and (min-width:30em){.swagger-ui .ba-ns{border-style:solid;border-width:1px}.swagger-ui .bt-ns{border-top-style:solid;border-top-width:1px}.swagger-ui .br-ns{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-ns{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-ns{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-ns{border-style:none;border-width:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ba-m{border-style:solid;border-width:1px}.swagger-ui .bt-m{border-top-style:solid;border-top-width:1px}.swagger-ui .br-m{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-m{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-m{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-m{border-style:none;border-width:0}}@media screen and (min-width:60em){.swagger-ui .ba-l{border-style:solid;border-width:1px}.swagger-ui .bt-l{border-top-style:solid;border-top-width:1px}.swagger-ui .br-l{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-l{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-l{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-l{border-style:none;border-width:0}}.swagger-ui .b--black{border-color:#000}.swagger-ui .b--near-black{border-color:#111}.swagger-ui .b--dark-gray{border-color:#333}.swagger-ui .b--mid-gray{border-color:#555}.swagger-ui .b--gray{border-color:#777}.swagger-ui .b--silver{border-color:#999}.swagger-ui .b--light-silver{border-color:#aaa}.swagger-ui .b--moon-gray{border-color:#ccc}.swagger-ui .b--light-gray{border-color:#eee}.swagger-ui .b--near-white{border-color:#f4f4f4}.swagger-ui .b--white{border-color:#fff}.swagger-ui .b--white-90{border-color:hsla(0,0%,100%,.9)}.swagger-ui .b--white-80{border-color:hsla(0,0%,100%,.8)}.swagger-ui .b--white-70{border-color:hsla(0,0%,100%,.7)}.swagger-ui .b--white-60{border-color:hsla(0,0%,100%,.6)}.swagger-ui .b--white-50{border-color:hsla(0,0%,100%,.5)}.swagger-ui .b--white-40{border-color:hsla(0,0%,100%,.4)}.swagger-ui .b--white-30{border-color:hsla(0,0%,100%,.3)}.swagger-ui .b--white-20{border-color:hsla(0,0%,100%,.2)}.swagger-ui .b--white-10{border-color:hsla(0,0%,100%,.1)}.swagger-ui .b--white-05{border-color:hsla(0,0%,100%,.05)}.swagger-ui .b--white-025{border-color:hsla(0,0%,100%,.025)}.swagger-ui .b--white-0125{border-color:hsla(0,0%,100%,.013)}.swagger-ui .b--black-90{border-color:rgba(0,0,0,.9)}.swagger-ui .b--black-80{border-color:rgba(0,0,0,.8)}.swagger-ui .b--black-70{border-color:rgba(0,0,0,.7)}.swagger-ui .b--black-60{border-color:rgba(0,0,0,.6)}.swagger-ui .b--black-50{border-color:rgba(0,0,0,.5)}.swagger-ui .b--black-40{border-color:rgba(0,0,0,.4)}.swagger-ui .b--black-30{border-color:rgba(0,0,0,.3)}.swagger-ui .b--black-20{border-color:rgba(0,0,0,.2)}.swagger-ui .b--black-10{border-color:rgba(0,0,0,.1)}.swagger-ui .b--black-05{border-color:rgba(0,0,0,.05)}.swagger-ui .b--black-025{border-color:rgba(0,0,0,.025)}.swagger-ui .b--black-0125{border-color:rgba(0,0,0,.013)}.swagger-ui .b--dark-red{border-color:#e7040f}.swagger-ui .b--red{border-color:#ff4136}.swagger-ui .b--light-red{border-color:#ff725c}.swagger-ui .b--orange{border-color:#ff6300}.swagger-ui .b--gold{border-color:#ffb700}.swagger-ui .b--yellow{border-color:gold}.swagger-ui .b--light-yellow{border-color:#fbf1a9}.swagger-ui .b--purple{border-color:#5e2ca5}.swagger-ui .b--light-purple{border-color:#a463f2}.swagger-ui .b--dark-pink{border-color:#d5008f}.swagger-ui .b--hot-pink{border-color:#ff41b4}.swagger-ui .b--pink{border-color:#ff80cc}.swagger-ui .b--light-pink{border-color:#ffa3d7}.swagger-ui .b--dark-green{border-color:#137752}.swagger-ui .b--green{border-color:#19a974}.swagger-ui .b--light-green{border-color:#9eebcf}.swagger-ui .b--navy{border-color:#001b44}.swagger-ui .b--dark-blue{border-color:#00449e}.swagger-ui .b--blue{border-color:#357edd}.swagger-ui .b--light-blue{border-color:#96ccff}.swagger-ui .b--lightest-blue{border-color:#cdecff}.swagger-ui .b--washed-blue{border-color:#f6fffe}.swagger-ui .b--washed-green{border-color:#e8fdf5}.swagger-ui .b--washed-yellow{border-color:#fffceb}.swagger-ui .b--washed-red{border-color:#ffdfdf}.swagger-ui .b--transparent{border-color:transparent}.swagger-ui .b--inherit{border-color:inherit}.swagger-ui .br0{border-radius:0}.swagger-ui .br1{border-radius:.125rem}.swagger-ui .br2{border-radius:.25rem}.swagger-ui .br3{border-radius:.5rem}.swagger-ui .br4{border-radius:1rem}.swagger-ui .br-100{border-radius:100%}.swagger-ui .br-pill{border-radius:9999px}.swagger-ui .br--bottom{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left{border-bottom-right-radius:0;border-top-right-radius:0}@media screen and (min-width:30em){.swagger-ui .br0-ns{border-radius:0}.swagger-ui .br1-ns{border-radius:.125rem}.swagger-ui .br2-ns{border-radius:.25rem}.swagger-ui .br3-ns{border-radius:.5rem}.swagger-ui .br4-ns{border-radius:1rem}.swagger-ui .br-100-ns{border-radius:100%}.swagger-ui .br-pill-ns{border-radius:9999px}.swagger-ui .br--bottom-ns{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-ns{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-ns{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-ns{border-bottom-right-radius:0;border-top-right-radius:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .br0-m{border-radius:0}.swagger-ui .br1-m{border-radius:.125rem}.swagger-ui .br2-m{border-radius:.25rem}.swagger-ui .br3-m{border-radius:.5rem}.swagger-ui .br4-m{border-radius:1rem}.swagger-ui .br-100-m{border-radius:100%}.swagger-ui .br-pill-m{border-radius:9999px}.swagger-ui .br--bottom-m{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-m{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-m{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-m{border-bottom-right-radius:0;border-top-right-radius:0}}@media screen and (min-width:60em){.swagger-ui .br0-l{border-radius:0}.swagger-ui .br1-l{border-radius:.125rem}.swagger-ui .br2-l{border-radius:.25rem}.swagger-ui .br3-l{border-radius:.5rem}.swagger-ui .br4-l{border-radius:1rem}.swagger-ui .br-100-l{border-radius:100%}.swagger-ui .br-pill-l{border-radius:9999px}.swagger-ui .br--bottom-l{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-l{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-l{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-l{border-bottom-right-radius:0;border-top-right-radius:0}}.swagger-ui .b--dotted{border-style:dotted}.swagger-ui .b--dashed{border-style:dashed}.swagger-ui .b--solid{border-style:solid}.swagger-ui .b--none{border-style:none}@media screen and (min-width:30em){.swagger-ui .b--dotted-ns{border-style:dotted}.swagger-ui .b--dashed-ns{border-style:dashed}.swagger-ui .b--solid-ns{border-style:solid}.swagger-ui .b--none-ns{border-style:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .b--dotted-m{border-style:dotted}.swagger-ui .b--dashed-m{border-style:dashed}.swagger-ui .b--solid-m{border-style:solid}.swagger-ui .b--none-m{border-style:none}}@media screen and (min-width:60em){.swagger-ui .b--dotted-l{border-style:dotted}.swagger-ui .b--dashed-l{border-style:dashed}.swagger-ui .b--solid-l{border-style:solid}.swagger-ui .b--none-l{border-style:none}}.swagger-ui .bw0{border-width:0}.swagger-ui .bw1{border-width:.125rem}.swagger-ui .bw2{border-width:.25rem}.swagger-ui .bw3{border-width:.5rem}.swagger-ui .bw4{border-width:1rem}.swagger-ui .bw5{border-width:2rem}.swagger-ui .bt-0{border-top-width:0}.swagger-ui .br-0{border-right-width:0}.swagger-ui .bb-0{border-bottom-width:0}.swagger-ui .bl-0{border-left-width:0}@media screen and (min-width:30em){.swagger-ui .bw0-ns{border-width:0}.swagger-ui .bw1-ns{border-width:.125rem}.swagger-ui .bw2-ns{border-width:.25rem}.swagger-ui .bw3-ns{border-width:.5rem}.swagger-ui .bw4-ns{border-width:1rem}.swagger-ui .bw5-ns{border-width:2rem}.swagger-ui .bt-0-ns{border-top-width:0}.swagger-ui .br-0-ns{border-right-width:0}.swagger-ui .bb-0-ns{border-bottom-width:0}.swagger-ui .bl-0-ns{border-left-width:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .bw0-m{border-width:0}.swagger-ui .bw1-m{border-width:.125rem}.swagger-ui .bw2-m{border-width:.25rem}.swagger-ui .bw3-m{border-width:.5rem}.swagger-ui .bw4-m{border-width:1rem}.swagger-ui .bw5-m{border-width:2rem}.swagger-ui .bt-0-m{border-top-width:0}.swagger-ui .br-0-m{border-right-width:0}.swagger-ui .bb-0-m{border-bottom-width:0}.swagger-ui .bl-0-m{border-left-width:0}}@media screen and (min-width:60em){.swagger-ui .bw0-l{border-width:0}.swagger-ui .bw1-l{border-width:.125rem}.swagger-ui .bw2-l{border-width:.25rem}.swagger-ui .bw3-l{border-width:.5rem}.swagger-ui .bw4-l{border-width:1rem}.swagger-ui .bw5-l{border-width:2rem}.swagger-ui .bt-0-l{border-top-width:0}.swagger-ui .br-0-l{border-right-width:0}.swagger-ui .bb-0-l{border-bottom-width:0}.swagger-ui .bl-0-l{border-left-width:0}}.swagger-ui .shadow-1{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}@media screen and (min-width:30em){.swagger-ui .shadow-1-ns{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-ns{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-ns{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-ns{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-ns{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .shadow-1-m{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-m{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-m{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-m{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-m{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}@media screen and (min-width:60em){.swagger-ui .shadow-1-l{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-l{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-l{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-l{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-l{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}.swagger-ui .pre{overflow-x:auto;overflow-y:hidden;overflow:scroll}.swagger-ui .top-0{top:0}.swagger-ui .right-0{right:0}.swagger-ui .bottom-0{bottom:0}.swagger-ui .left-0{left:0}.swagger-ui .top-1{top:1rem}.swagger-ui .right-1{right:1rem}.swagger-ui .bottom-1{bottom:1rem}.swagger-ui .left-1{left:1rem}.swagger-ui .top-2{top:2rem}.swagger-ui .right-2{right:2rem}.swagger-ui .bottom-2{bottom:2rem}.swagger-ui .left-2{left:2rem}.swagger-ui .top--1{top:-1rem}.swagger-ui .right--1{right:-1rem}.swagger-ui .bottom--1{bottom:-1rem}.swagger-ui .left--1{left:-1rem}.swagger-ui .top--2{top:-2rem}.swagger-ui .right--2{right:-2rem}.swagger-ui .bottom--2{bottom:-2rem}.swagger-ui .left--2{left:-2rem}.swagger-ui .absolute--fill{bottom:0;left:0;right:0;top:0}@media screen and (min-width:30em){.swagger-ui .top-0-ns{top:0}.swagger-ui .left-0-ns{left:0}.swagger-ui .right-0-ns{right:0}.swagger-ui .bottom-0-ns{bottom:0}.swagger-ui .top-1-ns{top:1rem}.swagger-ui .left-1-ns{left:1rem}.swagger-ui .right-1-ns{right:1rem}.swagger-ui .bottom-1-ns{bottom:1rem}.swagger-ui .top-2-ns{top:2rem}.swagger-ui .left-2-ns{left:2rem}.swagger-ui .right-2-ns{right:2rem}.swagger-ui .bottom-2-ns{bottom:2rem}.swagger-ui .top--1-ns{top:-1rem}.swagger-ui .right--1-ns{right:-1rem}.swagger-ui .bottom--1-ns{bottom:-1rem}.swagger-ui .left--1-ns{left:-1rem}.swagger-ui .top--2-ns{top:-2rem}.swagger-ui .right--2-ns{right:-2rem}.swagger-ui .bottom--2-ns{bottom:-2rem}.swagger-ui .left--2-ns{left:-2rem}.swagger-ui .absolute--fill-ns{bottom:0;left:0;right:0;top:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .top-0-m{top:0}.swagger-ui .left-0-m{left:0}.swagger-ui .right-0-m{right:0}.swagger-ui .bottom-0-m{bottom:0}.swagger-ui .top-1-m{top:1rem}.swagger-ui .left-1-m{left:1rem}.swagger-ui .right-1-m{right:1rem}.swagger-ui .bottom-1-m{bottom:1rem}.swagger-ui .top-2-m{top:2rem}.swagger-ui .left-2-m{left:2rem}.swagger-ui .right-2-m{right:2rem}.swagger-ui .bottom-2-m{bottom:2rem}.swagger-ui .top--1-m{top:-1rem}.swagger-ui .right--1-m{right:-1rem}.swagger-ui .bottom--1-m{bottom:-1rem}.swagger-ui .left--1-m{left:-1rem}.swagger-ui .top--2-m{top:-2rem}.swagger-ui .right--2-m{right:-2rem}.swagger-ui .bottom--2-m{bottom:-2rem}.swagger-ui .left--2-m{left:-2rem}.swagger-ui .absolute--fill-m{bottom:0;left:0;right:0;top:0}}@media screen and (min-width:60em){.swagger-ui .top-0-l{top:0}.swagger-ui .left-0-l{left:0}.swagger-ui .right-0-l{right:0}.swagger-ui .bottom-0-l{bottom:0}.swagger-ui .top-1-l{top:1rem}.swagger-ui .left-1-l{left:1rem}.swagger-ui .right-1-l{right:1rem}.swagger-ui .bottom-1-l{bottom:1rem}.swagger-ui .top-2-l{top:2rem}.swagger-ui .left-2-l{left:2rem}.swagger-ui .right-2-l{right:2rem}.swagger-ui .bottom-2-l{bottom:2rem}.swagger-ui .top--1-l{top:-1rem}.swagger-ui .right--1-l{right:-1rem}.swagger-ui .bottom--1-l{bottom:-1rem}.swagger-ui .left--1-l{left:-1rem}.swagger-ui .top--2-l{top:-2rem}.swagger-ui .right--2-l{right:-2rem}.swagger-ui .bottom--2-l{bottom:-2rem}.swagger-ui .left--2-l{left:-2rem}.swagger-ui .absolute--fill-l{bottom:0;left:0;right:0;top:0}}.swagger-ui .cf:after,.swagger-ui .cf:before{content:" ";display:table}.swagger-ui .cf:after{clear:both}.swagger-ui .cf{zoom:1}.swagger-ui .cl{clear:left}.swagger-ui .cr{clear:right}.swagger-ui .cb{clear:both}.swagger-ui .cn{clear:none}@media screen and (min-width:30em){.swagger-ui .cl-ns{clear:left}.swagger-ui .cr-ns{clear:right}.swagger-ui .cb-ns{clear:both}.swagger-ui .cn-ns{clear:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .cl-m{clear:left}.swagger-ui .cr-m{clear:right}.swagger-ui .cb-m{clear:both}.swagger-ui .cn-m{clear:none}}@media screen and (min-width:60em){.swagger-ui .cl-l{clear:left}.swagger-ui .cr-l{clear:right}.swagger-ui .cb-l{clear:both}.swagger-ui .cn-l{clear:none}}.swagger-ui .flex{display:flex}.swagger-ui .inline-flex{display:inline-flex}.swagger-ui .flex-auto{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none{flex:none}.swagger-ui .flex-column{flex-direction:column}.swagger-ui .flex-row{flex-direction:row}.swagger-ui .flex-wrap{flex-wrap:wrap}.swagger-ui .flex-nowrap{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse{flex-direction:column-reverse}.swagger-ui .flex-row-reverse{flex-direction:row-reverse}.swagger-ui .items-start{align-items:flex-start}.swagger-ui .items-end{align-items:flex-end}.swagger-ui .items-center{align-items:center}.swagger-ui .items-baseline{align-items:baseline}.swagger-ui .items-stretch{align-items:stretch}.swagger-ui .self-start{align-self:flex-start}.swagger-ui .self-end{align-self:flex-end}.swagger-ui .self-center{align-self:center}.swagger-ui .self-baseline{align-self:baseline}.swagger-ui .self-stretch{align-self:stretch}.swagger-ui .justify-start{justify-content:flex-start}.swagger-ui .justify-end{justify-content:flex-end}.swagger-ui .justify-center{justify-content:center}.swagger-ui .justify-between{justify-content:space-between}.swagger-ui .justify-around{justify-content:space-around}.swagger-ui .content-start{align-content:flex-start}.swagger-ui .content-end{align-content:flex-end}.swagger-ui .content-center{align-content:center}.swagger-ui .content-between{align-content:space-between}.swagger-ui .content-around{align-content:space-around}.swagger-ui .content-stretch{align-content:stretch}.swagger-ui .order-0{order:0}.swagger-ui .order-1{order:1}.swagger-ui .order-2{order:2}.swagger-ui .order-3{order:3}.swagger-ui .order-4{order:4}.swagger-ui .order-5{order:5}.swagger-ui .order-6{order:6}.swagger-ui .order-7{order:7}.swagger-ui .order-8{order:8}.swagger-ui .order-last{order:99999}.swagger-ui .flex-grow-0{flex-grow:0}.swagger-ui .flex-grow-1{flex-grow:1}.swagger-ui .flex-shrink-0{flex-shrink:0}.swagger-ui .flex-shrink-1{flex-shrink:1}@media screen and (min-width:30em){.swagger-ui .flex-ns{display:flex}.swagger-ui .inline-flex-ns{display:inline-flex}.swagger-ui .flex-auto-ns{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-ns{flex:none}.swagger-ui .flex-column-ns{flex-direction:column}.swagger-ui .flex-row-ns{flex-direction:row}.swagger-ui .flex-wrap-ns{flex-wrap:wrap}.swagger-ui .flex-nowrap-ns{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-ns{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-ns{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-ns{flex-direction:row-reverse}.swagger-ui .items-start-ns{align-items:flex-start}.swagger-ui .items-end-ns{align-items:flex-end}.swagger-ui .items-center-ns{align-items:center}.swagger-ui .items-baseline-ns{align-items:baseline}.swagger-ui .items-stretch-ns{align-items:stretch}.swagger-ui .self-start-ns{align-self:flex-start}.swagger-ui .self-end-ns{align-self:flex-end}.swagger-ui .self-center-ns{align-self:center}.swagger-ui .self-baseline-ns{align-self:baseline}.swagger-ui .self-stretch-ns{align-self:stretch}.swagger-ui .justify-start-ns{justify-content:flex-start}.swagger-ui .justify-end-ns{justify-content:flex-end}.swagger-ui .justify-center-ns{justify-content:center}.swagger-ui .justify-between-ns{justify-content:space-between}.swagger-ui .justify-around-ns{justify-content:space-around}.swagger-ui .content-start-ns{align-content:flex-start}.swagger-ui .content-end-ns{align-content:flex-end}.swagger-ui .content-center-ns{align-content:center}.swagger-ui .content-between-ns{align-content:space-between}.swagger-ui .content-around-ns{align-content:space-around}.swagger-ui .content-stretch-ns{align-content:stretch}.swagger-ui .order-0-ns{order:0}.swagger-ui .order-1-ns{order:1}.swagger-ui .order-2-ns{order:2}.swagger-ui .order-3-ns{order:3}.swagger-ui .order-4-ns{order:4}.swagger-ui .order-5-ns{order:5}.swagger-ui .order-6-ns{order:6}.swagger-ui .order-7-ns{order:7}.swagger-ui .order-8-ns{order:8}.swagger-ui .order-last-ns{order:99999}.swagger-ui .flex-grow-0-ns{flex-grow:0}.swagger-ui .flex-grow-1-ns{flex-grow:1}.swagger-ui .flex-shrink-0-ns{flex-shrink:0}.swagger-ui .flex-shrink-1-ns{flex-shrink:1}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .flex-m{display:flex}.swagger-ui .inline-flex-m{display:inline-flex}.swagger-ui .flex-auto-m{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-m{flex:none}.swagger-ui .flex-column-m{flex-direction:column}.swagger-ui .flex-row-m{flex-direction:row}.swagger-ui .flex-wrap-m{flex-wrap:wrap}.swagger-ui .flex-nowrap-m{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-m{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-m{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-m{flex-direction:row-reverse}.swagger-ui .items-start-m{align-items:flex-start}.swagger-ui .items-end-m{align-items:flex-end}.swagger-ui .items-center-m{align-items:center}.swagger-ui .items-baseline-m{align-items:baseline}.swagger-ui .items-stretch-m{align-items:stretch}.swagger-ui .self-start-m{align-self:flex-start}.swagger-ui .self-end-m{align-self:flex-end}.swagger-ui .self-center-m{align-self:center}.swagger-ui .self-baseline-m{align-self:baseline}.swagger-ui .self-stretch-m{align-self:stretch}.swagger-ui .justify-start-m{justify-content:flex-start}.swagger-ui .justify-end-m{justify-content:flex-end}.swagger-ui .justify-center-m{justify-content:center}.swagger-ui .justify-between-m{justify-content:space-between}.swagger-ui .justify-around-m{justify-content:space-around}.swagger-ui .content-start-m{align-content:flex-start}.swagger-ui .content-end-m{align-content:flex-end}.swagger-ui .content-center-m{align-content:center}.swagger-ui .content-between-m{align-content:space-between}.swagger-ui .content-around-m{align-content:space-around}.swagger-ui .content-stretch-m{align-content:stretch}.swagger-ui .order-0-m{order:0}.swagger-ui .order-1-m{order:1}.swagger-ui .order-2-m{order:2}.swagger-ui .order-3-m{order:3}.swagger-ui .order-4-m{order:4}.swagger-ui .order-5-m{order:5}.swagger-ui .order-6-m{order:6}.swagger-ui .order-7-m{order:7}.swagger-ui .order-8-m{order:8}.swagger-ui .order-last-m{order:99999}.swagger-ui .flex-grow-0-m{flex-grow:0}.swagger-ui .flex-grow-1-m{flex-grow:1}.swagger-ui .flex-shrink-0-m{flex-shrink:0}.swagger-ui .flex-shrink-1-m{flex-shrink:1}}@media screen and (min-width:60em){.swagger-ui .flex-l{display:flex}.swagger-ui .inline-flex-l{display:inline-flex}.swagger-ui .flex-auto-l{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-l{flex:none}.swagger-ui .flex-column-l{flex-direction:column}.swagger-ui .flex-row-l{flex-direction:row}.swagger-ui .flex-wrap-l{flex-wrap:wrap}.swagger-ui .flex-nowrap-l{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-l{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-l{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-l{flex-direction:row-reverse}.swagger-ui .items-start-l{align-items:flex-start}.swagger-ui .items-end-l{align-items:flex-end}.swagger-ui .items-center-l{align-items:center}.swagger-ui .items-baseline-l{align-items:baseline}.swagger-ui .items-stretch-l{align-items:stretch}.swagger-ui .self-start-l{align-self:flex-start}.swagger-ui .self-end-l{align-self:flex-end}.swagger-ui .self-center-l{align-self:center}.swagger-ui .self-baseline-l{align-self:baseline}.swagger-ui .self-stretch-l{align-self:stretch}.swagger-ui .justify-start-l{justify-content:flex-start}.swagger-ui .justify-end-l{justify-content:flex-end}.swagger-ui .justify-center-l{justify-content:center}.swagger-ui .justify-between-l{justify-content:space-between}.swagger-ui .justify-around-l{justify-content:space-around}.swagger-ui .content-start-l{align-content:flex-start}.swagger-ui .content-end-l{align-content:flex-end}.swagger-ui .content-center-l{align-content:center}.swagger-ui .content-between-l{align-content:space-between}.swagger-ui .content-around-l{align-content:space-around}.swagger-ui .content-stretch-l{align-content:stretch}.swagger-ui .order-0-l{order:0}.swagger-ui .order-1-l{order:1}.swagger-ui .order-2-l{order:2}.swagger-ui .order-3-l{order:3}.swagger-ui .order-4-l{order:4}.swagger-ui .order-5-l{order:5}.swagger-ui .order-6-l{order:6}.swagger-ui .order-7-l{order:7}.swagger-ui .order-8-l{order:8}.swagger-ui .order-last-l{order:99999}.swagger-ui .flex-grow-0-l{flex-grow:0}.swagger-ui .flex-grow-1-l{flex-grow:1}.swagger-ui .flex-shrink-0-l{flex-shrink:0}.swagger-ui .flex-shrink-1-l{flex-shrink:1}}.swagger-ui .dn{display:none}.swagger-ui .di{display:inline}.swagger-ui .db{display:block}.swagger-ui .dib{display:inline-block}.swagger-ui .dit{display:inline-table}.swagger-ui .dt{display:table}.swagger-ui .dtc{display:table-cell}.swagger-ui .dt-row{display:table-row}.swagger-ui .dt-row-group{display:table-row-group}.swagger-ui .dt-column{display:table-column}.swagger-ui .dt-column-group{display:table-column-group}.swagger-ui .dt--fixed{table-layout:fixed;width:100%}@media screen and (min-width:30em){.swagger-ui .dn-ns{display:none}.swagger-ui .di-ns{display:inline}.swagger-ui .db-ns{display:block}.swagger-ui .dib-ns{display:inline-block}.swagger-ui .dit-ns{display:inline-table}.swagger-ui .dt-ns{display:table}.swagger-ui .dtc-ns{display:table-cell}.swagger-ui .dt-row-ns{display:table-row}.swagger-ui .dt-row-group-ns{display:table-row-group}.swagger-ui .dt-column-ns{display:table-column}.swagger-ui .dt-column-group-ns{display:table-column-group}.swagger-ui .dt--fixed-ns{table-layout:fixed;width:100%}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .dn-m{display:none}.swagger-ui .di-m{display:inline}.swagger-ui .db-m{display:block}.swagger-ui .dib-m{display:inline-block}.swagger-ui .dit-m{display:inline-table}.swagger-ui .dt-m{display:table}.swagger-ui .dtc-m{display:table-cell}.swagger-ui .dt-row-m{display:table-row}.swagger-ui .dt-row-group-m{display:table-row-group}.swagger-ui .dt-column-m{display:table-column}.swagger-ui .dt-column-group-m{display:table-column-group}.swagger-ui .dt--fixed-m{table-layout:fixed;width:100%}}@media screen and (min-width:60em){.swagger-ui .dn-l{display:none}.swagger-ui .di-l{display:inline}.swagger-ui .db-l{display:block}.swagger-ui .dib-l{display:inline-block}.swagger-ui .dit-l{display:inline-table}.swagger-ui .dt-l{display:table}.swagger-ui .dtc-l{display:table-cell}.swagger-ui .dt-row-l{display:table-row}.swagger-ui .dt-row-group-l{display:table-row-group}.swagger-ui .dt-column-l{display:table-column}.swagger-ui .dt-column-group-l{display:table-column-group}.swagger-ui .dt--fixed-l{table-layout:fixed;width:100%}}.swagger-ui .fl{_display:inline;float:left}.swagger-ui .fr{_display:inline;float:right}.swagger-ui .fn{float:none}@media screen and (min-width:30em){.swagger-ui .fl-ns{_display:inline;float:left}.swagger-ui .fr-ns{_display:inline;float:right}.swagger-ui .fn-ns{float:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .fl-m{_display:inline;float:left}.swagger-ui .fr-m{_display:inline;float:right}.swagger-ui .fn-m{float:none}}@media screen and (min-width:60em){.swagger-ui .fl-l{_display:inline;float:left}.swagger-ui .fr-l{_display:inline;float:right}.swagger-ui .fn-l{float:none}}.swagger-ui .sans-serif{font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica,helvetica neue,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.swagger-ui .serif{font-family:georgia,serif}.swagger-ui .system-sans-serif{font-family:sans-serif}.swagger-ui .system-serif{font-family:serif}.swagger-ui .code,.swagger-ui code{font-family:Consolas,monaco,monospace}.swagger-ui .courier{font-family:Courier Next,courier,monospace}.swagger-ui .helvetica{font-family:helvetica neue,helvetica,sans-serif}.swagger-ui .avenir{font-family:avenir next,avenir,sans-serif}.swagger-ui .athelas{font-family:athelas,georgia,serif}.swagger-ui .georgia{font-family:georgia,serif}.swagger-ui .times{font-family:times,serif}.swagger-ui .bodoni{font-family:Bodoni MT,serif}.swagger-ui .calisto{font-family:Calisto MT,serif}.swagger-ui .garamond{font-family:garamond,serif}.swagger-ui .baskerville{font-family:baskerville,serif}.swagger-ui .i{font-style:italic}.swagger-ui .fs-normal{font-style:normal}@media screen and (min-width:30em){.swagger-ui .i-ns{font-style:italic}.swagger-ui .fs-normal-ns{font-style:normal}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .i-m{font-style:italic}.swagger-ui .fs-normal-m{font-style:normal}}@media screen and (min-width:60em){.swagger-ui .i-l{font-style:italic}.swagger-ui .fs-normal-l{font-style:normal}}.swagger-ui .normal{font-weight:400}.swagger-ui .b{font-weight:700}.swagger-ui .fw1{font-weight:100}.swagger-ui .fw2{font-weight:200}.swagger-ui .fw3{font-weight:300}.swagger-ui .fw4{font-weight:400}.swagger-ui .fw5{font-weight:500}.swagger-ui .fw6{font-weight:600}.swagger-ui .fw7{font-weight:700}.swagger-ui .fw8{font-weight:800}.swagger-ui .fw9{font-weight:900}@media screen and (min-width:30em){.swagger-ui .normal-ns{font-weight:400}.swagger-ui .b-ns{font-weight:700}.swagger-ui .fw1-ns{font-weight:100}.swagger-ui .fw2-ns{font-weight:200}.swagger-ui .fw3-ns{font-weight:300}.swagger-ui .fw4-ns{font-weight:400}.swagger-ui .fw5-ns{font-weight:500}.swagger-ui .fw6-ns{font-weight:600}.swagger-ui .fw7-ns{font-weight:700}.swagger-ui .fw8-ns{font-weight:800}.swagger-ui .fw9-ns{font-weight:900}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .normal-m{font-weight:400}.swagger-ui .b-m{font-weight:700}.swagger-ui .fw1-m{font-weight:100}.swagger-ui .fw2-m{font-weight:200}.swagger-ui .fw3-m{font-weight:300}.swagger-ui .fw4-m{font-weight:400}.swagger-ui .fw5-m{font-weight:500}.swagger-ui .fw6-m{font-weight:600}.swagger-ui .fw7-m{font-weight:700}.swagger-ui .fw8-m{font-weight:800}.swagger-ui .fw9-m{font-weight:900}}@media screen and (min-width:60em){.swagger-ui .normal-l{font-weight:400}.swagger-ui .b-l{font-weight:700}.swagger-ui .fw1-l{font-weight:100}.swagger-ui .fw2-l{font-weight:200}.swagger-ui .fw3-l{font-weight:300}.swagger-ui .fw4-l{font-weight:400}.swagger-ui .fw5-l{font-weight:500}.swagger-ui .fw6-l{font-weight:600}.swagger-ui .fw7-l{font-weight:700}.swagger-ui .fw8-l{font-weight:800}.swagger-ui .fw9-l{font-weight:900}}.swagger-ui .input-reset{-webkit-appearance:none;-moz-appearance:none}.swagger-ui .button-reset::-moz-focus-inner,.swagger-ui .input-reset::-moz-focus-inner{border:0;padding:0}.swagger-ui .h1{height:1rem}.swagger-ui .h2{height:2rem}.swagger-ui .h3{height:4rem}.swagger-ui .h4{height:8rem}.swagger-ui .h5{height:16rem}.swagger-ui .h-25{height:25%}.swagger-ui .h-50{height:50%}.swagger-ui .h-75{height:75%}.swagger-ui .h-100{height:100%}.swagger-ui .min-h-100{min-height:100%}.swagger-ui .vh-25{height:25vh}.swagger-ui .vh-50{height:50vh}.swagger-ui .vh-75{height:75vh}.swagger-ui .vh-100{height:100vh}.swagger-ui .min-vh-100{min-height:100vh}.swagger-ui .h-auto{height:auto}.swagger-ui .h-inherit{height:inherit}@media screen and (min-width:30em){.swagger-ui .h1-ns{height:1rem}.swagger-ui .h2-ns{height:2rem}.swagger-ui .h3-ns{height:4rem}.swagger-ui .h4-ns{height:8rem}.swagger-ui .h5-ns{height:16rem}.swagger-ui .h-25-ns{height:25%}.swagger-ui .h-50-ns{height:50%}.swagger-ui .h-75-ns{height:75%}.swagger-ui .h-100-ns{height:100%}.swagger-ui .min-h-100-ns{min-height:100%}.swagger-ui .vh-25-ns{height:25vh}.swagger-ui .vh-50-ns{height:50vh}.swagger-ui .vh-75-ns{height:75vh}.swagger-ui .vh-100-ns{height:100vh}.swagger-ui .min-vh-100-ns{min-height:100vh}.swagger-ui .h-auto-ns{height:auto}.swagger-ui .h-inherit-ns{height:inherit}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .h1-m{height:1rem}.swagger-ui .h2-m{height:2rem}.swagger-ui .h3-m{height:4rem}.swagger-ui .h4-m{height:8rem}.swagger-ui .h5-m{height:16rem}.swagger-ui .h-25-m{height:25%}.swagger-ui .h-50-m{height:50%}.swagger-ui .h-75-m{height:75%}.swagger-ui .h-100-m{height:100%}.swagger-ui .min-h-100-m{min-height:100%}.swagger-ui .vh-25-m{height:25vh}.swagger-ui .vh-50-m{height:50vh}.swagger-ui .vh-75-m{height:75vh}.swagger-ui .vh-100-m{height:100vh}.swagger-ui .min-vh-100-m{min-height:100vh}.swagger-ui .h-auto-m{height:auto}.swagger-ui .h-inherit-m{height:inherit}}@media screen and (min-width:60em){.swagger-ui .h1-l{height:1rem}.swagger-ui .h2-l{height:2rem}.swagger-ui .h3-l{height:4rem}.swagger-ui .h4-l{height:8rem}.swagger-ui .h5-l{height:16rem}.swagger-ui .h-25-l{height:25%}.swagger-ui .h-50-l{height:50%}.swagger-ui .h-75-l{height:75%}.swagger-ui .h-100-l{height:100%}.swagger-ui .min-h-100-l{min-height:100%}.swagger-ui .vh-25-l{height:25vh}.swagger-ui .vh-50-l{height:50vh}.swagger-ui .vh-75-l{height:75vh}.swagger-ui .vh-100-l{height:100vh}.swagger-ui .min-vh-100-l{min-height:100vh}.swagger-ui .h-auto-l{height:auto}.swagger-ui .h-inherit-l{height:inherit}}.swagger-ui .tracked{letter-spacing:.1em}.swagger-ui .tracked-tight{letter-spacing:-.05em}.swagger-ui .tracked-mega{letter-spacing:.25em}@media screen and (min-width:30em){.swagger-ui .tracked-ns{letter-spacing:.1em}.swagger-ui .tracked-tight-ns{letter-spacing:-.05em}.swagger-ui .tracked-mega-ns{letter-spacing:.25em}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .tracked-m{letter-spacing:.1em}.swagger-ui .tracked-tight-m{letter-spacing:-.05em}.swagger-ui .tracked-mega-m{letter-spacing:.25em}}@media screen and (min-width:60em){.swagger-ui .tracked-l{letter-spacing:.1em}.swagger-ui .tracked-tight-l{letter-spacing:-.05em}.swagger-ui .tracked-mega-l{letter-spacing:.25em}}.swagger-ui .lh-solid{line-height:1}.swagger-ui .lh-title{line-height:1.25}.swagger-ui .lh-copy{line-height:1.5}@media screen and (min-width:30em){.swagger-ui .lh-solid-ns{line-height:1}.swagger-ui .lh-title-ns{line-height:1.25}.swagger-ui .lh-copy-ns{line-height:1.5}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .lh-solid-m{line-height:1}.swagger-ui .lh-title-m{line-height:1.25}.swagger-ui .lh-copy-m{line-height:1.5}}@media screen and (min-width:60em){.swagger-ui .lh-solid-l{line-height:1}.swagger-ui .lh-title-l{line-height:1.25}.swagger-ui .lh-copy-l{line-height:1.5}}.swagger-ui .link{-webkit-text-decoration:none;text-decoration:none}.swagger-ui .link,.swagger-ui .link:active,.swagger-ui .link:focus,.swagger-ui .link:hover,.swagger-ui .link:link,.swagger-ui .link:visited{transition:color .15s ease-in}.swagger-ui .link:focus{outline:1px dotted currentColor}.swagger-ui .list{list-style-type:none}.swagger-ui .mw-100{max-width:100%}.swagger-ui .mw1{max-width:1rem}.swagger-ui .mw2{max-width:2rem}.swagger-ui .mw3{max-width:4rem}.swagger-ui .mw4{max-width:8rem}.swagger-ui .mw5{max-width:16rem}.swagger-ui .mw6{max-width:32rem}.swagger-ui .mw7{max-width:48rem}.swagger-ui .mw8{max-width:64rem}.swagger-ui .mw9{max-width:96rem}.swagger-ui .mw-none{max-width:none}@media screen and (min-width:30em){.swagger-ui .mw-100-ns{max-width:100%}.swagger-ui .mw1-ns{max-width:1rem}.swagger-ui .mw2-ns{max-width:2rem}.swagger-ui .mw3-ns{max-width:4rem}.swagger-ui .mw4-ns{max-width:8rem}.swagger-ui .mw5-ns{max-width:16rem}.swagger-ui .mw6-ns{max-width:32rem}.swagger-ui .mw7-ns{max-width:48rem}.swagger-ui .mw8-ns{max-width:64rem}.swagger-ui .mw9-ns{max-width:96rem}.swagger-ui .mw-none-ns{max-width:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .mw-100-m{max-width:100%}.swagger-ui .mw1-m{max-width:1rem}.swagger-ui .mw2-m{max-width:2rem}.swagger-ui .mw3-m{max-width:4rem}.swagger-ui .mw4-m{max-width:8rem}.swagger-ui .mw5-m{max-width:16rem}.swagger-ui .mw6-m{max-width:32rem}.swagger-ui .mw7-m{max-width:48rem}.swagger-ui .mw8-m{max-width:64rem}.swagger-ui .mw9-m{max-width:96rem}.swagger-ui .mw-none-m{max-width:none}}@media screen and (min-width:60em){.swagger-ui .mw-100-l{max-width:100%}.swagger-ui .mw1-l{max-width:1rem}.swagger-ui .mw2-l{max-width:2rem}.swagger-ui .mw3-l{max-width:4rem}.swagger-ui .mw4-l{max-width:8rem}.swagger-ui .mw5-l{max-width:16rem}.swagger-ui .mw6-l{max-width:32rem}.swagger-ui .mw7-l{max-width:48rem}.swagger-ui .mw8-l{max-width:64rem}.swagger-ui .mw9-l{max-width:96rem}.swagger-ui .mw-none-l{max-width:none}}.swagger-ui .w1{width:1rem}.swagger-ui .w2{width:2rem}.swagger-ui .w3{width:4rem}.swagger-ui .w4{width:8rem}.swagger-ui .w5{width:16rem}.swagger-ui .w-10{width:10%}.swagger-ui .w-20{width:20%}.swagger-ui .w-25{width:25%}.swagger-ui .w-30{width:30%}.swagger-ui .w-33{width:33%}.swagger-ui .w-34{width:34%}.swagger-ui .w-40{width:40%}.swagger-ui .w-50{width:50%}.swagger-ui .w-60{width:60%}.swagger-ui .w-70{width:70%}.swagger-ui .w-75{width:75%}.swagger-ui .w-80{width:80%}.swagger-ui .w-90{width:90%}.swagger-ui .w-100{width:100%}.swagger-ui .w-third{width:33.3333333333%}.swagger-ui .w-two-thirds{width:66.6666666667%}.swagger-ui .w-auto{width:auto}@media screen and (min-width:30em){.swagger-ui .w1-ns{width:1rem}.swagger-ui .w2-ns{width:2rem}.swagger-ui .w3-ns{width:4rem}.swagger-ui .w4-ns{width:8rem}.swagger-ui .w5-ns{width:16rem}.swagger-ui .w-10-ns{width:10%}.swagger-ui .w-20-ns{width:20%}.swagger-ui .w-25-ns{width:25%}.swagger-ui .w-30-ns{width:30%}.swagger-ui .w-33-ns{width:33%}.swagger-ui .w-34-ns{width:34%}.swagger-ui .w-40-ns{width:40%}.swagger-ui .w-50-ns{width:50%}.swagger-ui .w-60-ns{width:60%}.swagger-ui .w-70-ns{width:70%}.swagger-ui .w-75-ns{width:75%}.swagger-ui .w-80-ns{width:80%}.swagger-ui .w-90-ns{width:90%}.swagger-ui .w-100-ns{width:100%}.swagger-ui .w-third-ns{width:33.3333333333%}.swagger-ui .w-two-thirds-ns{width:66.6666666667%}.swagger-ui .w-auto-ns{width:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .w1-m{width:1rem}.swagger-ui .w2-m{width:2rem}.swagger-ui .w3-m{width:4rem}.swagger-ui .w4-m{width:8rem}.swagger-ui .w5-m{width:16rem}.swagger-ui .w-10-m{width:10%}.swagger-ui .w-20-m{width:20%}.swagger-ui .w-25-m{width:25%}.swagger-ui .w-30-m{width:30%}.swagger-ui .w-33-m{width:33%}.swagger-ui .w-34-m{width:34%}.swagger-ui .w-40-m{width:40%}.swagger-ui .w-50-m{width:50%}.swagger-ui .w-60-m{width:60%}.swagger-ui .w-70-m{width:70%}.swagger-ui .w-75-m{width:75%}.swagger-ui .w-80-m{width:80%}.swagger-ui .w-90-m{width:90%}.swagger-ui .w-100-m{width:100%}.swagger-ui .w-third-m{width:33.3333333333%}.swagger-ui .w-two-thirds-m{width:66.6666666667%}.swagger-ui .w-auto-m{width:auto}}@media screen and (min-width:60em){.swagger-ui .w1-l{width:1rem}.swagger-ui .w2-l{width:2rem}.swagger-ui .w3-l{width:4rem}.swagger-ui .w4-l{width:8rem}.swagger-ui .w5-l{width:16rem}.swagger-ui .w-10-l{width:10%}.swagger-ui .w-20-l{width:20%}.swagger-ui .w-25-l{width:25%}.swagger-ui .w-30-l{width:30%}.swagger-ui .w-33-l{width:33%}.swagger-ui .w-34-l{width:34%}.swagger-ui .w-40-l{width:40%}.swagger-ui .w-50-l{width:50%}.swagger-ui .w-60-l{width:60%}.swagger-ui .w-70-l{width:70%}.swagger-ui .w-75-l{width:75%}.swagger-ui .w-80-l{width:80%}.swagger-ui .w-90-l{width:90%}.swagger-ui .w-100-l{width:100%}.swagger-ui .w-third-l{width:33.3333333333%}.swagger-ui .w-two-thirds-l{width:66.6666666667%}.swagger-ui .w-auto-l{width:auto}}.swagger-ui .overflow-visible{overflow:visible}.swagger-ui .overflow-hidden{overflow:hidden}.swagger-ui .overflow-scroll{overflow:scroll}.swagger-ui .overflow-auto{overflow:auto}.swagger-ui .overflow-x-visible{overflow-x:visible}.swagger-ui .overflow-x-hidden{overflow-x:hidden}.swagger-ui .overflow-x-scroll{overflow-x:scroll}.swagger-ui .overflow-x-auto{overflow-x:auto}.swagger-ui .overflow-y-visible{overflow-y:visible}.swagger-ui .overflow-y-hidden{overflow-y:hidden}.swagger-ui .overflow-y-scroll{overflow-y:scroll}.swagger-ui .overflow-y-auto{overflow-y:auto}@media screen and (min-width:30em){.swagger-ui .overflow-visible-ns{overflow:visible}.swagger-ui .overflow-hidden-ns{overflow:hidden}.swagger-ui .overflow-scroll-ns{overflow:scroll}.swagger-ui .overflow-auto-ns{overflow:auto}.swagger-ui .overflow-x-visible-ns{overflow-x:visible}.swagger-ui .overflow-x-hidden-ns{overflow-x:hidden}.swagger-ui .overflow-x-scroll-ns{overflow-x:scroll}.swagger-ui .overflow-x-auto-ns{overflow-x:auto}.swagger-ui .overflow-y-visible-ns{overflow-y:visible}.swagger-ui .overflow-y-hidden-ns{overflow-y:hidden}.swagger-ui .overflow-y-scroll-ns{overflow-y:scroll}.swagger-ui .overflow-y-auto-ns{overflow-y:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .overflow-visible-m{overflow:visible}.swagger-ui .overflow-hidden-m{overflow:hidden}.swagger-ui .overflow-scroll-m{overflow:scroll}.swagger-ui .overflow-auto-m{overflow:auto}.swagger-ui .overflow-x-visible-m{overflow-x:visible}.swagger-ui .overflow-x-hidden-m{overflow-x:hidden}.swagger-ui .overflow-x-scroll-m{overflow-x:scroll}.swagger-ui .overflow-x-auto-m{overflow-x:auto}.swagger-ui .overflow-y-visible-m{overflow-y:visible}.swagger-ui .overflow-y-hidden-m{overflow-y:hidden}.swagger-ui .overflow-y-scroll-m{overflow-y:scroll}.swagger-ui .overflow-y-auto-m{overflow-y:auto}}@media screen and (min-width:60em){.swagger-ui .overflow-visible-l{overflow:visible}.swagger-ui .overflow-hidden-l{overflow:hidden}.swagger-ui .overflow-scroll-l{overflow:scroll}.swagger-ui .overflow-auto-l{overflow:auto}.swagger-ui .overflow-x-visible-l{overflow-x:visible}.swagger-ui .overflow-x-hidden-l{overflow-x:hidden}.swagger-ui .overflow-x-scroll-l{overflow-x:scroll}.swagger-ui .overflow-x-auto-l{overflow-x:auto}.swagger-ui .overflow-y-visible-l{overflow-y:visible}.swagger-ui .overflow-y-hidden-l{overflow-y:hidden}.swagger-ui .overflow-y-scroll-l{overflow-y:scroll}.swagger-ui .overflow-y-auto-l{overflow-y:auto}}.swagger-ui .static{position:static}.swagger-ui .relative{position:relative}.swagger-ui .absolute{position:absolute}.swagger-ui .fixed{position:fixed}@media screen and (min-width:30em){.swagger-ui .static-ns{position:static}.swagger-ui .relative-ns{position:relative}.swagger-ui .absolute-ns{position:absolute}.swagger-ui .fixed-ns{position:fixed}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .static-m{position:static}.swagger-ui .relative-m{position:relative}.swagger-ui .absolute-m{position:absolute}.swagger-ui .fixed-m{position:fixed}}@media screen and (min-width:60em){.swagger-ui .static-l{position:static}.swagger-ui .relative-l{position:relative}.swagger-ui .absolute-l{position:absolute}.swagger-ui .fixed-l{position:fixed}}.swagger-ui .o-100{opacity:1}.swagger-ui .o-90{opacity:.9}.swagger-ui .o-80{opacity:.8}.swagger-ui .o-70{opacity:.7}.swagger-ui .o-60{opacity:.6}.swagger-ui .o-50{opacity:.5}.swagger-ui .o-40{opacity:.4}.swagger-ui .o-30{opacity:.3}.swagger-ui .o-20{opacity:.2}.swagger-ui .o-10{opacity:.1}.swagger-ui .o-05{opacity:.05}.swagger-ui .o-025{opacity:.025}.swagger-ui .o-0{opacity:0}.swagger-ui .rotate-45{transform:rotate(45deg)}.swagger-ui .rotate-90{transform:rotate(90deg)}.swagger-ui .rotate-135{transform:rotate(135deg)}.swagger-ui .rotate-180{transform:rotate(180deg)}.swagger-ui .rotate-225{transform:rotate(225deg)}.swagger-ui .rotate-270{transform:rotate(270deg)}.swagger-ui .rotate-315{transform:rotate(315deg)}@media screen and (min-width:30em){.swagger-ui .rotate-45-ns{transform:rotate(45deg)}.swagger-ui .rotate-90-ns{transform:rotate(90deg)}.swagger-ui .rotate-135-ns{transform:rotate(135deg)}.swagger-ui .rotate-180-ns{transform:rotate(180deg)}.swagger-ui .rotate-225-ns{transform:rotate(225deg)}.swagger-ui .rotate-270-ns{transform:rotate(270deg)}.swagger-ui .rotate-315-ns{transform:rotate(315deg)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .rotate-45-m{transform:rotate(45deg)}.swagger-ui .rotate-90-m{transform:rotate(90deg)}.swagger-ui .rotate-135-m{transform:rotate(135deg)}.swagger-ui .rotate-180-m{transform:rotate(180deg)}.swagger-ui .rotate-225-m{transform:rotate(225deg)}.swagger-ui .rotate-270-m{transform:rotate(270deg)}.swagger-ui .rotate-315-m{transform:rotate(315deg)}}@media screen and (min-width:60em){.swagger-ui .rotate-45-l{transform:rotate(45deg)}.swagger-ui .rotate-90-l{transform:rotate(90deg)}.swagger-ui .rotate-135-l{transform:rotate(135deg)}.swagger-ui .rotate-180-l{transform:rotate(180deg)}.swagger-ui .rotate-225-l{transform:rotate(225deg)}.swagger-ui .rotate-270-l{transform:rotate(270deg)}.swagger-ui .rotate-315-l{transform:rotate(315deg)}}.swagger-ui .black-90{color:rgba(0,0,0,.9)}.swagger-ui .black-80{color:rgba(0,0,0,.8)}.swagger-ui .black-70{color:rgba(0,0,0,.7)}.swagger-ui .black-60{color:rgba(0,0,0,.6)}.swagger-ui .black-50{color:rgba(0,0,0,.5)}.swagger-ui .black-40{color:rgba(0,0,0,.4)}.swagger-ui .black-30{color:rgba(0,0,0,.3)}.swagger-ui .black-20{color:rgba(0,0,0,.2)}.swagger-ui .black-10{color:rgba(0,0,0,.1)}.swagger-ui .black-05{color:rgba(0,0,0,.05)}.swagger-ui .white-90{color:hsla(0,0%,100%,.9)}.swagger-ui .white-80{color:hsla(0,0%,100%,.8)}.swagger-ui .white-70{color:hsla(0,0%,100%,.7)}.swagger-ui .white-60{color:hsla(0,0%,100%,.6)}.swagger-ui .white-50{color:hsla(0,0%,100%,.5)}.swagger-ui .white-40{color:hsla(0,0%,100%,.4)}.swagger-ui .white-30{color:hsla(0,0%,100%,.3)}.swagger-ui .white-20{color:hsla(0,0%,100%,.2)}.swagger-ui .white-10{color:hsla(0,0%,100%,.1)}.swagger-ui .black{color:#000}.swagger-ui .near-black{color:#111}.swagger-ui .dark-gray{color:#333}.swagger-ui .mid-gray{color:#555}.swagger-ui .gray{color:#777}.swagger-ui .silver{color:#999}.swagger-ui .light-silver{color:#aaa}.swagger-ui .moon-gray{color:#ccc}.swagger-ui .light-gray{color:#eee}.swagger-ui .near-white{color:#f4f4f4}.swagger-ui .white{color:#fff}.swagger-ui .dark-red{color:#e7040f}.swagger-ui .red{color:#ff4136}.swagger-ui .light-red{color:#ff725c}.swagger-ui .orange{color:#ff6300}.swagger-ui .gold{color:#ffb700}.swagger-ui .yellow{color:gold}.swagger-ui .light-yellow{color:#fbf1a9}.swagger-ui .purple{color:#5e2ca5}.swagger-ui .light-purple{color:#a463f2}.swagger-ui .dark-pink{color:#d5008f}.swagger-ui .hot-pink{color:#ff41b4}.swagger-ui .pink{color:#ff80cc}.swagger-ui .light-pink{color:#ffa3d7}.swagger-ui .dark-green{color:#137752}.swagger-ui .green{color:#19a974}.swagger-ui .light-green{color:#9eebcf}.swagger-ui .navy{color:#001b44}.swagger-ui .dark-blue{color:#00449e}.swagger-ui .blue{color:#357edd}.swagger-ui .light-blue{color:#96ccff}.swagger-ui .lightest-blue{color:#cdecff}.swagger-ui .washed-blue{color:#f6fffe}.swagger-ui .washed-green{color:#e8fdf5}.swagger-ui .washed-yellow{color:#fffceb}.swagger-ui .washed-red{color:#ffdfdf}.swagger-ui .color-inherit{color:inherit}.swagger-ui .bg-black-90{background-color:rgba(0,0,0,.9)}.swagger-ui .bg-black-80{background-color:rgba(0,0,0,.8)}.swagger-ui .bg-black-70{background-color:rgba(0,0,0,.7)}.swagger-ui .bg-black-60{background-color:rgba(0,0,0,.6)}.swagger-ui .bg-black-50{background-color:rgba(0,0,0,.5)}.swagger-ui .bg-black-40{background-color:rgba(0,0,0,.4)}.swagger-ui .bg-black-30{background-color:rgba(0,0,0,.3)}.swagger-ui .bg-black-20{background-color:rgba(0,0,0,.2)}.swagger-ui .bg-black-10{background-color:rgba(0,0,0,.1)}.swagger-ui .bg-black-05{background-color:rgba(0,0,0,.05)}.swagger-ui .bg-white-90{background-color:hsla(0,0%,100%,.9)}.swagger-ui .bg-white-80{background-color:hsla(0,0%,100%,.8)}.swagger-ui .bg-white-70{background-color:hsla(0,0%,100%,.7)}.swagger-ui .bg-white-60{background-color:hsla(0,0%,100%,.6)}.swagger-ui .bg-white-50{background-color:hsla(0,0%,100%,.5)}.swagger-ui .bg-white-40{background-color:hsla(0,0%,100%,.4)}.swagger-ui .bg-white-30{background-color:hsla(0,0%,100%,.3)}.swagger-ui .bg-white-20{background-color:hsla(0,0%,100%,.2)}.swagger-ui .bg-white-10{background-color:hsla(0,0%,100%,.1)}.swagger-ui .bg-black{background-color:#000}.swagger-ui .bg-near-black{background-color:#111}.swagger-ui .bg-dark-gray{background-color:#333}.swagger-ui .bg-mid-gray{background-color:#555}.swagger-ui .bg-gray{background-color:#777}.swagger-ui .bg-silver{background-color:#999}.swagger-ui .bg-light-silver{background-color:#aaa}.swagger-ui .bg-moon-gray{background-color:#ccc}.swagger-ui .bg-light-gray{background-color:#eee}.swagger-ui .bg-near-white{background-color:#f4f4f4}.swagger-ui .bg-white{background-color:#fff}.swagger-ui .bg-transparent{background-color:transparent}.swagger-ui .bg-dark-red{background-color:#e7040f}.swagger-ui .bg-red{background-color:#ff4136}.swagger-ui .bg-light-red{background-color:#ff725c}.swagger-ui .bg-orange{background-color:#ff6300}.swagger-ui .bg-gold{background-color:#ffb700}.swagger-ui .bg-yellow{background-color:gold}.swagger-ui .bg-light-yellow{background-color:#fbf1a9}.swagger-ui .bg-purple{background-color:#5e2ca5}.swagger-ui .bg-light-purple{background-color:#a463f2}.swagger-ui .bg-dark-pink{background-color:#d5008f}.swagger-ui .bg-hot-pink{background-color:#ff41b4}.swagger-ui .bg-pink{background-color:#ff80cc}.swagger-ui .bg-light-pink{background-color:#ffa3d7}.swagger-ui .bg-dark-green{background-color:#137752}.swagger-ui .bg-green{background-color:#19a974}.swagger-ui .bg-light-green{background-color:#9eebcf}.swagger-ui .bg-navy{background-color:#001b44}.swagger-ui .bg-dark-blue{background-color:#00449e}.swagger-ui .bg-blue{background-color:#357edd}.swagger-ui .bg-light-blue{background-color:#96ccff}.swagger-ui .bg-lightest-blue{background-color:#cdecff}.swagger-ui .bg-washed-blue{background-color:#f6fffe}.swagger-ui .bg-washed-green{background-color:#e8fdf5}.swagger-ui .bg-washed-yellow{background-color:#fffceb}.swagger-ui .bg-washed-red{background-color:#ffdfdf}.swagger-ui .bg-inherit{background-color:inherit}.swagger-ui .hover-black:focus,.swagger-ui .hover-black:hover{color:#000}.swagger-ui .hover-near-black:focus,.swagger-ui .hover-near-black:hover{color:#111}.swagger-ui .hover-dark-gray:focus,.swagger-ui .hover-dark-gray:hover{color:#333}.swagger-ui .hover-mid-gray:focus,.swagger-ui .hover-mid-gray:hover{color:#555}.swagger-ui .hover-gray:focus,.swagger-ui .hover-gray:hover{color:#777}.swagger-ui .hover-silver:focus,.swagger-ui .hover-silver:hover{color:#999}.swagger-ui .hover-light-silver:focus,.swagger-ui .hover-light-silver:hover{color:#aaa}.swagger-ui .hover-moon-gray:focus,.swagger-ui .hover-moon-gray:hover{color:#ccc}.swagger-ui .hover-light-gray:focus,.swagger-ui .hover-light-gray:hover{color:#eee}.swagger-ui .hover-near-white:focus,.swagger-ui .hover-near-white:hover{color:#f4f4f4}.swagger-ui .hover-white:focus,.swagger-ui .hover-white:hover{color:#fff}.swagger-ui .hover-black-90:focus,.swagger-ui .hover-black-90:hover{color:rgba(0,0,0,.9)}.swagger-ui .hover-black-80:focus,.swagger-ui .hover-black-80:hover{color:rgba(0,0,0,.8)}.swagger-ui .hover-black-70:focus,.swagger-ui .hover-black-70:hover{color:rgba(0,0,0,.7)}.swagger-ui .hover-black-60:focus,.swagger-ui .hover-black-60:hover{color:rgba(0,0,0,.6)}.swagger-ui .hover-black-50:focus,.swagger-ui .hover-black-50:hover{color:rgba(0,0,0,.5)}.swagger-ui .hover-black-40:focus,.swagger-ui .hover-black-40:hover{color:rgba(0,0,0,.4)}.swagger-ui .hover-black-30:focus,.swagger-ui .hover-black-30:hover{color:rgba(0,0,0,.3)}.swagger-ui .hover-black-20:focus,.swagger-ui .hover-black-20:hover{color:rgba(0,0,0,.2)}.swagger-ui .hover-black-10:focus,.swagger-ui .hover-black-10:hover{color:rgba(0,0,0,.1)}.swagger-ui .hover-white-90:focus,.swagger-ui .hover-white-90:hover{color:hsla(0,0%,100%,.9)}.swagger-ui .hover-white-80:focus,.swagger-ui .hover-white-80:hover{color:hsla(0,0%,100%,.8)}.swagger-ui .hover-white-70:focus,.swagger-ui .hover-white-70:hover{color:hsla(0,0%,100%,.7)}.swagger-ui .hover-white-60:focus,.swagger-ui .hover-white-60:hover{color:hsla(0,0%,100%,.6)}.swagger-ui .hover-white-50:focus,.swagger-ui .hover-white-50:hover{color:hsla(0,0%,100%,.5)}.swagger-ui .hover-white-40:focus,.swagger-ui .hover-white-40:hover{color:hsla(0,0%,100%,.4)}.swagger-ui .hover-white-30:focus,.swagger-ui .hover-white-30:hover{color:hsla(0,0%,100%,.3)}.swagger-ui .hover-white-20:focus,.swagger-ui .hover-white-20:hover{color:hsla(0,0%,100%,.2)}.swagger-ui .hover-white-10:focus,.swagger-ui .hover-white-10:hover{color:hsla(0,0%,100%,.1)}.swagger-ui .hover-inherit:focus,.swagger-ui .hover-inherit:hover{color:inherit}.swagger-ui .hover-bg-black:focus,.swagger-ui .hover-bg-black:hover{background-color:#000}.swagger-ui .hover-bg-near-black:focus,.swagger-ui .hover-bg-near-black:hover{background-color:#111}.swagger-ui .hover-bg-dark-gray:focus,.swagger-ui .hover-bg-dark-gray:hover{background-color:#333}.swagger-ui .hover-bg-mid-gray:focus,.swagger-ui .hover-bg-mid-gray:hover{background-color:#555}.swagger-ui .hover-bg-gray:focus,.swagger-ui .hover-bg-gray:hover{background-color:#777}.swagger-ui .hover-bg-silver:focus,.swagger-ui .hover-bg-silver:hover{background-color:#999}.swagger-ui .hover-bg-light-silver:focus,.swagger-ui .hover-bg-light-silver:hover{background-color:#aaa}.swagger-ui .hover-bg-moon-gray:focus,.swagger-ui .hover-bg-moon-gray:hover{background-color:#ccc}.swagger-ui .hover-bg-light-gray:focus,.swagger-ui .hover-bg-light-gray:hover{background-color:#eee}.swagger-ui .hover-bg-near-white:focus,.swagger-ui .hover-bg-near-white:hover{background-color:#f4f4f4}.swagger-ui .hover-bg-white:focus,.swagger-ui .hover-bg-white:hover{background-color:#fff}.swagger-ui .hover-bg-transparent:focus,.swagger-ui .hover-bg-transparent:hover{background-color:transparent}.swagger-ui .hover-bg-black-90:focus,.swagger-ui .hover-bg-black-90:hover{background-color:rgba(0,0,0,.9)}.swagger-ui .hover-bg-black-80:focus,.swagger-ui .hover-bg-black-80:hover{background-color:rgba(0,0,0,.8)}.swagger-ui .hover-bg-black-70:focus,.swagger-ui .hover-bg-black-70:hover{background-color:rgba(0,0,0,.7)}.swagger-ui .hover-bg-black-60:focus,.swagger-ui .hover-bg-black-60:hover{background-color:rgba(0,0,0,.6)}.swagger-ui .hover-bg-black-50:focus,.swagger-ui .hover-bg-black-50:hover{background-color:rgba(0,0,0,.5)}.swagger-ui .hover-bg-black-40:focus,.swagger-ui .hover-bg-black-40:hover{background-color:rgba(0,0,0,.4)}.swagger-ui .hover-bg-black-30:focus,.swagger-ui .hover-bg-black-30:hover{background-color:rgba(0,0,0,.3)}.swagger-ui .hover-bg-black-20:focus,.swagger-ui .hover-bg-black-20:hover{background-color:rgba(0,0,0,.2)}.swagger-ui .hover-bg-black-10:focus,.swagger-ui .hover-bg-black-10:hover{background-color:rgba(0,0,0,.1)}.swagger-ui .hover-bg-white-90:focus,.swagger-ui .hover-bg-white-90:hover{background-color:hsla(0,0%,100%,.9)}.swagger-ui .hover-bg-white-80:focus,.swagger-ui .hover-bg-white-80:hover{background-color:hsla(0,0%,100%,.8)}.swagger-ui .hover-bg-white-70:focus,.swagger-ui .hover-bg-white-70:hover{background-color:hsla(0,0%,100%,.7)}.swagger-ui .hover-bg-white-60:focus,.swagger-ui .hover-bg-white-60:hover{background-color:hsla(0,0%,100%,.6)}.swagger-ui .hover-bg-white-50:focus,.swagger-ui .hover-bg-white-50:hover{background-color:hsla(0,0%,100%,.5)}.swagger-ui .hover-bg-white-40:focus,.swagger-ui .hover-bg-white-40:hover{background-color:hsla(0,0%,100%,.4)}.swagger-ui .hover-bg-white-30:focus,.swagger-ui .hover-bg-white-30:hover{background-color:hsla(0,0%,100%,.3)}.swagger-ui .hover-bg-white-20:focus,.swagger-ui .hover-bg-white-20:hover{background-color:hsla(0,0%,100%,.2)}.swagger-ui .hover-bg-white-10:focus,.swagger-ui .hover-bg-white-10:hover{background-color:hsla(0,0%,100%,.1)}.swagger-ui .hover-dark-red:focus,.swagger-ui .hover-dark-red:hover{color:#e7040f}.swagger-ui .hover-red:focus,.swagger-ui .hover-red:hover{color:#ff4136}.swagger-ui .hover-light-red:focus,.swagger-ui .hover-light-red:hover{color:#ff725c}.swagger-ui .hover-orange:focus,.swagger-ui .hover-orange:hover{color:#ff6300}.swagger-ui .hover-gold:focus,.swagger-ui .hover-gold:hover{color:#ffb700}.swagger-ui .hover-yellow:focus,.swagger-ui .hover-yellow:hover{color:gold}.swagger-ui .hover-light-yellow:focus,.swagger-ui .hover-light-yellow:hover{color:#fbf1a9}.swagger-ui .hover-purple:focus,.swagger-ui .hover-purple:hover{color:#5e2ca5}.swagger-ui .hover-light-purple:focus,.swagger-ui .hover-light-purple:hover{color:#a463f2}.swagger-ui .hover-dark-pink:focus,.swagger-ui .hover-dark-pink:hover{color:#d5008f}.swagger-ui .hover-hot-pink:focus,.swagger-ui .hover-hot-pink:hover{color:#ff41b4}.swagger-ui .hover-pink:focus,.swagger-ui .hover-pink:hover{color:#ff80cc}.swagger-ui .hover-light-pink:focus,.swagger-ui .hover-light-pink:hover{color:#ffa3d7}.swagger-ui .hover-dark-green:focus,.swagger-ui .hover-dark-green:hover{color:#137752}.swagger-ui .hover-green:focus,.swagger-ui .hover-green:hover{color:#19a974}.swagger-ui .hover-light-green:focus,.swagger-ui .hover-light-green:hover{color:#9eebcf}.swagger-ui .hover-navy:focus,.swagger-ui .hover-navy:hover{color:#001b44}.swagger-ui .hover-dark-blue:focus,.swagger-ui .hover-dark-blue:hover{color:#00449e}.swagger-ui .hover-blue:focus,.swagger-ui .hover-blue:hover{color:#357edd}.swagger-ui .hover-light-blue:focus,.swagger-ui .hover-light-blue:hover{color:#96ccff}.swagger-ui .hover-lightest-blue:focus,.swagger-ui .hover-lightest-blue:hover{color:#cdecff}.swagger-ui .hover-washed-blue:focus,.swagger-ui .hover-washed-blue:hover{color:#f6fffe}.swagger-ui .hover-washed-green:focus,.swagger-ui .hover-washed-green:hover{color:#e8fdf5}.swagger-ui .hover-washed-yellow:focus,.swagger-ui .hover-washed-yellow:hover{color:#fffceb}.swagger-ui .hover-washed-red:focus,.swagger-ui .hover-washed-red:hover{color:#ffdfdf}.swagger-ui .hover-bg-dark-red:focus,.swagger-ui .hover-bg-dark-red:hover{background-color:#e7040f}.swagger-ui .hover-bg-red:focus,.swagger-ui .hover-bg-red:hover{background-color:#ff4136}.swagger-ui .hover-bg-light-red:focus,.swagger-ui .hover-bg-light-red:hover{background-color:#ff725c}.swagger-ui .hover-bg-orange:focus,.swagger-ui .hover-bg-orange:hover{background-color:#ff6300}.swagger-ui .hover-bg-gold:focus,.swagger-ui .hover-bg-gold:hover{background-color:#ffb700}.swagger-ui .hover-bg-yellow:focus,.swagger-ui .hover-bg-yellow:hover{background-color:gold}.swagger-ui .hover-bg-light-yellow:focus,.swagger-ui .hover-bg-light-yellow:hover{background-color:#fbf1a9}.swagger-ui .hover-bg-purple:focus,.swagger-ui .hover-bg-purple:hover{background-color:#5e2ca5}.swagger-ui .hover-bg-light-purple:focus,.swagger-ui .hover-bg-light-purple:hover{background-color:#a463f2}.swagger-ui .hover-bg-dark-pink:focus,.swagger-ui .hover-bg-dark-pink:hover{background-color:#d5008f}.swagger-ui .hover-bg-hot-pink:focus,.swagger-ui .hover-bg-hot-pink:hover{background-color:#ff41b4}.swagger-ui .hover-bg-pink:focus,.swagger-ui .hover-bg-pink:hover{background-color:#ff80cc}.swagger-ui .hover-bg-light-pink:focus,.swagger-ui .hover-bg-light-pink:hover{background-color:#ffa3d7}.swagger-ui .hover-bg-dark-green:focus,.swagger-ui .hover-bg-dark-green:hover{background-color:#137752}.swagger-ui .hover-bg-green:focus,.swagger-ui .hover-bg-green:hover{background-color:#19a974}.swagger-ui .hover-bg-light-green:focus,.swagger-ui .hover-bg-light-green:hover{background-color:#9eebcf}.swagger-ui .hover-bg-navy:focus,.swagger-ui .hover-bg-navy:hover{background-color:#001b44}.swagger-ui .hover-bg-dark-blue:focus,.swagger-ui .hover-bg-dark-blue:hover{background-color:#00449e}.swagger-ui .hover-bg-blue:focus,.swagger-ui .hover-bg-blue:hover{background-color:#357edd}.swagger-ui .hover-bg-light-blue:focus,.swagger-ui .hover-bg-light-blue:hover{background-color:#96ccff}.swagger-ui .hover-bg-lightest-blue:focus,.swagger-ui .hover-bg-lightest-blue:hover{background-color:#cdecff}.swagger-ui .hover-bg-washed-blue:focus,.swagger-ui .hover-bg-washed-blue:hover{background-color:#f6fffe}.swagger-ui .hover-bg-washed-green:focus,.swagger-ui .hover-bg-washed-green:hover{background-color:#e8fdf5}.swagger-ui .hover-bg-washed-yellow:focus,.swagger-ui .hover-bg-washed-yellow:hover{background-color:#fffceb}.swagger-ui .hover-bg-washed-red:focus,.swagger-ui .hover-bg-washed-red:hover{background-color:#ffdfdf}.swagger-ui .hover-bg-inherit:focus,.swagger-ui .hover-bg-inherit:hover{background-color:inherit}.swagger-ui .pa0{padding:0}.swagger-ui .pa1{padding:.25rem}.swagger-ui .pa2{padding:.5rem}.swagger-ui .pa3{padding:1rem}.swagger-ui .pa4{padding:2rem}.swagger-ui .pa5{padding:4rem}.swagger-ui .pa6{padding:8rem}.swagger-ui .pa7{padding:16rem}.swagger-ui .pl0{padding-left:0}.swagger-ui .pl1{padding-left:.25rem}.swagger-ui .pl2{padding-left:.5rem}.swagger-ui .pl3{padding-left:1rem}.swagger-ui .pl4{padding-left:2rem}.swagger-ui .pl5{padding-left:4rem}.swagger-ui .pl6{padding-left:8rem}.swagger-ui .pl7{padding-left:16rem}.swagger-ui .pr0{padding-right:0}.swagger-ui .pr1{padding-right:.25rem}.swagger-ui .pr2{padding-right:.5rem}.swagger-ui .pr3{padding-right:1rem}.swagger-ui .pr4{padding-right:2rem}.swagger-ui .pr5{padding-right:4rem}.swagger-ui .pr6{padding-right:8rem}.swagger-ui .pr7{padding-right:16rem}.swagger-ui .pb0{padding-bottom:0}.swagger-ui .pb1{padding-bottom:.25rem}.swagger-ui .pb2{padding-bottom:.5rem}.swagger-ui .pb3{padding-bottom:1rem}.swagger-ui .pb4{padding-bottom:2rem}.swagger-ui .pb5{padding-bottom:4rem}.swagger-ui .pb6{padding-bottom:8rem}.swagger-ui .pb7{padding-bottom:16rem}.swagger-ui .pt0{padding-top:0}.swagger-ui .pt1{padding-top:.25rem}.swagger-ui .pt2{padding-top:.5rem}.swagger-ui .pt3{padding-top:1rem}.swagger-ui .pt4{padding-top:2rem}.swagger-ui .pt5{padding-top:4rem}.swagger-ui .pt6{padding-top:8rem}.swagger-ui .pt7{padding-top:16rem}.swagger-ui .pv0{padding-bottom:0;padding-top:0}.swagger-ui .pv1{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0{padding-left:0;padding-right:0}.swagger-ui .ph1{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0{margin:0}.swagger-ui .ma1{margin:.25rem}.swagger-ui .ma2{margin:.5rem}.swagger-ui .ma3{margin:1rem}.swagger-ui .ma4{margin:2rem}.swagger-ui .ma5{margin:4rem}.swagger-ui .ma6{margin:8rem}.swagger-ui .ma7{margin:16rem}.swagger-ui .ml0{margin-left:0}.swagger-ui .ml1{margin-left:.25rem}.swagger-ui .ml2{margin-left:.5rem}.swagger-ui .ml3{margin-left:1rem}.swagger-ui .ml4{margin-left:2rem}.swagger-ui .ml5{margin-left:4rem}.swagger-ui .ml6{margin-left:8rem}.swagger-ui .ml7{margin-left:16rem}.swagger-ui .mr0{margin-right:0}.swagger-ui .mr1{margin-right:.25rem}.swagger-ui .mr2{margin-right:.5rem}.swagger-ui .mr3{margin-right:1rem}.swagger-ui .mr4{margin-right:2rem}.swagger-ui .mr5{margin-right:4rem}.swagger-ui .mr6{margin-right:8rem}.swagger-ui .mr7{margin-right:16rem}.swagger-ui .mb0{margin-bottom:0}.swagger-ui .mb1{margin-bottom:.25rem}.swagger-ui .mb2{margin-bottom:.5rem}.swagger-ui .mb3{margin-bottom:1rem}.swagger-ui .mb4{margin-bottom:2rem}.swagger-ui .mb5{margin-bottom:4rem}.swagger-ui .mb6{margin-bottom:8rem}.swagger-ui .mb7{margin-bottom:16rem}.swagger-ui .mt0{margin-top:0}.swagger-ui .mt1{margin-top:.25rem}.swagger-ui .mt2{margin-top:.5rem}.swagger-ui .mt3{margin-top:1rem}.swagger-ui .mt4{margin-top:2rem}.swagger-ui .mt5{margin-top:4rem}.swagger-ui .mt6{margin-top:8rem}.swagger-ui .mt7{margin-top:16rem}.swagger-ui .mv0{margin-bottom:0;margin-top:0}.swagger-ui .mv1{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0{margin-left:0;margin-right:0}.swagger-ui .mh1{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7{margin-left:16rem;margin-right:16rem}@media screen and (min-width:30em){.swagger-ui .pa0-ns{padding:0}.swagger-ui .pa1-ns{padding:.25rem}.swagger-ui .pa2-ns{padding:.5rem}.swagger-ui .pa3-ns{padding:1rem}.swagger-ui .pa4-ns{padding:2rem}.swagger-ui .pa5-ns{padding:4rem}.swagger-ui .pa6-ns{padding:8rem}.swagger-ui .pa7-ns{padding:16rem}.swagger-ui .pl0-ns{padding-left:0}.swagger-ui .pl1-ns{padding-left:.25rem}.swagger-ui .pl2-ns{padding-left:.5rem}.swagger-ui .pl3-ns{padding-left:1rem}.swagger-ui .pl4-ns{padding-left:2rem}.swagger-ui .pl5-ns{padding-left:4rem}.swagger-ui .pl6-ns{padding-left:8rem}.swagger-ui .pl7-ns{padding-left:16rem}.swagger-ui .pr0-ns{padding-right:0}.swagger-ui .pr1-ns{padding-right:.25rem}.swagger-ui .pr2-ns{padding-right:.5rem}.swagger-ui .pr3-ns{padding-right:1rem}.swagger-ui .pr4-ns{padding-right:2rem}.swagger-ui .pr5-ns{padding-right:4rem}.swagger-ui .pr6-ns{padding-right:8rem}.swagger-ui .pr7-ns{padding-right:16rem}.swagger-ui .pb0-ns{padding-bottom:0}.swagger-ui .pb1-ns{padding-bottom:.25rem}.swagger-ui .pb2-ns{padding-bottom:.5rem}.swagger-ui .pb3-ns{padding-bottom:1rem}.swagger-ui .pb4-ns{padding-bottom:2rem}.swagger-ui .pb5-ns{padding-bottom:4rem}.swagger-ui .pb6-ns{padding-bottom:8rem}.swagger-ui .pb7-ns{padding-bottom:16rem}.swagger-ui .pt0-ns{padding-top:0}.swagger-ui .pt1-ns{padding-top:.25rem}.swagger-ui .pt2-ns{padding-top:.5rem}.swagger-ui .pt3-ns{padding-top:1rem}.swagger-ui .pt4-ns{padding-top:2rem}.swagger-ui .pt5-ns{padding-top:4rem}.swagger-ui .pt6-ns{padding-top:8rem}.swagger-ui .pt7-ns{padding-top:16rem}.swagger-ui .pv0-ns{padding-bottom:0;padding-top:0}.swagger-ui .pv1-ns{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-ns{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-ns{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-ns{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-ns{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-ns{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-ns{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-ns{padding-left:0;padding-right:0}.swagger-ui .ph1-ns{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-ns{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-ns{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-ns{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-ns{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-ns{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-ns{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-ns{margin:0}.swagger-ui .ma1-ns{margin:.25rem}.swagger-ui .ma2-ns{margin:.5rem}.swagger-ui .ma3-ns{margin:1rem}.swagger-ui .ma4-ns{margin:2rem}.swagger-ui .ma5-ns{margin:4rem}.swagger-ui .ma6-ns{margin:8rem}.swagger-ui .ma7-ns{margin:16rem}.swagger-ui .ml0-ns{margin-left:0}.swagger-ui .ml1-ns{margin-left:.25rem}.swagger-ui .ml2-ns{margin-left:.5rem}.swagger-ui .ml3-ns{margin-left:1rem}.swagger-ui .ml4-ns{margin-left:2rem}.swagger-ui .ml5-ns{margin-left:4rem}.swagger-ui .ml6-ns{margin-left:8rem}.swagger-ui .ml7-ns{margin-left:16rem}.swagger-ui .mr0-ns{margin-right:0}.swagger-ui .mr1-ns{margin-right:.25rem}.swagger-ui .mr2-ns{margin-right:.5rem}.swagger-ui .mr3-ns{margin-right:1rem}.swagger-ui .mr4-ns{margin-right:2rem}.swagger-ui .mr5-ns{margin-right:4rem}.swagger-ui .mr6-ns{margin-right:8rem}.swagger-ui .mr7-ns{margin-right:16rem}.swagger-ui .mb0-ns{margin-bottom:0}.swagger-ui .mb1-ns{margin-bottom:.25rem}.swagger-ui .mb2-ns{margin-bottom:.5rem}.swagger-ui .mb3-ns{margin-bottom:1rem}.swagger-ui .mb4-ns{margin-bottom:2rem}.swagger-ui .mb5-ns{margin-bottom:4rem}.swagger-ui .mb6-ns{margin-bottom:8rem}.swagger-ui .mb7-ns{margin-bottom:16rem}.swagger-ui .mt0-ns{margin-top:0}.swagger-ui .mt1-ns{margin-top:.25rem}.swagger-ui .mt2-ns{margin-top:.5rem}.swagger-ui .mt3-ns{margin-top:1rem}.swagger-ui .mt4-ns{margin-top:2rem}.swagger-ui .mt5-ns{margin-top:4rem}.swagger-ui .mt6-ns{margin-top:8rem}.swagger-ui .mt7-ns{margin-top:16rem}.swagger-ui .mv0-ns{margin-bottom:0;margin-top:0}.swagger-ui .mv1-ns{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-ns{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-ns{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-ns{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-ns{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-ns{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-ns{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-ns{margin-left:0;margin-right:0}.swagger-ui .mh1-ns{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-ns{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-ns{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-ns{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-ns{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-ns{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-ns{margin-left:16rem;margin-right:16rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .pa0-m{padding:0}.swagger-ui .pa1-m{padding:.25rem}.swagger-ui .pa2-m{padding:.5rem}.swagger-ui .pa3-m{padding:1rem}.swagger-ui .pa4-m{padding:2rem}.swagger-ui .pa5-m{padding:4rem}.swagger-ui .pa6-m{padding:8rem}.swagger-ui .pa7-m{padding:16rem}.swagger-ui .pl0-m{padding-left:0}.swagger-ui .pl1-m{padding-left:.25rem}.swagger-ui .pl2-m{padding-left:.5rem}.swagger-ui .pl3-m{padding-left:1rem}.swagger-ui .pl4-m{padding-left:2rem}.swagger-ui .pl5-m{padding-left:4rem}.swagger-ui .pl6-m{padding-left:8rem}.swagger-ui .pl7-m{padding-left:16rem}.swagger-ui .pr0-m{padding-right:0}.swagger-ui .pr1-m{padding-right:.25rem}.swagger-ui .pr2-m{padding-right:.5rem}.swagger-ui .pr3-m{padding-right:1rem}.swagger-ui .pr4-m{padding-right:2rem}.swagger-ui .pr5-m{padding-right:4rem}.swagger-ui .pr6-m{padding-right:8rem}.swagger-ui .pr7-m{padding-right:16rem}.swagger-ui .pb0-m{padding-bottom:0}.swagger-ui .pb1-m{padding-bottom:.25rem}.swagger-ui .pb2-m{padding-bottom:.5rem}.swagger-ui .pb3-m{padding-bottom:1rem}.swagger-ui .pb4-m{padding-bottom:2rem}.swagger-ui .pb5-m{padding-bottom:4rem}.swagger-ui .pb6-m{padding-bottom:8rem}.swagger-ui .pb7-m{padding-bottom:16rem}.swagger-ui .pt0-m{padding-top:0}.swagger-ui .pt1-m{padding-top:.25rem}.swagger-ui .pt2-m{padding-top:.5rem}.swagger-ui .pt3-m{padding-top:1rem}.swagger-ui .pt4-m{padding-top:2rem}.swagger-ui .pt5-m{padding-top:4rem}.swagger-ui .pt6-m{padding-top:8rem}.swagger-ui .pt7-m{padding-top:16rem}.swagger-ui .pv0-m{padding-bottom:0;padding-top:0}.swagger-ui .pv1-m{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-m{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-m{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-m{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-m{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-m{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-m{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-m{padding-left:0;padding-right:0}.swagger-ui .ph1-m{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-m{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-m{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-m{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-m{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-m{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-m{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-m{margin:0}.swagger-ui .ma1-m{margin:.25rem}.swagger-ui .ma2-m{margin:.5rem}.swagger-ui .ma3-m{margin:1rem}.swagger-ui .ma4-m{margin:2rem}.swagger-ui .ma5-m{margin:4rem}.swagger-ui .ma6-m{margin:8rem}.swagger-ui .ma7-m{margin:16rem}.swagger-ui .ml0-m{margin-left:0}.swagger-ui .ml1-m{margin-left:.25rem}.swagger-ui .ml2-m{margin-left:.5rem}.swagger-ui .ml3-m{margin-left:1rem}.swagger-ui .ml4-m{margin-left:2rem}.swagger-ui .ml5-m{margin-left:4rem}.swagger-ui .ml6-m{margin-left:8rem}.swagger-ui .ml7-m{margin-left:16rem}.swagger-ui .mr0-m{margin-right:0}.swagger-ui .mr1-m{margin-right:.25rem}.swagger-ui .mr2-m{margin-right:.5rem}.swagger-ui .mr3-m{margin-right:1rem}.swagger-ui .mr4-m{margin-right:2rem}.swagger-ui .mr5-m{margin-right:4rem}.swagger-ui .mr6-m{margin-right:8rem}.swagger-ui .mr7-m{margin-right:16rem}.swagger-ui .mb0-m{margin-bottom:0}.swagger-ui .mb1-m{margin-bottom:.25rem}.swagger-ui .mb2-m{margin-bottom:.5rem}.swagger-ui .mb3-m{margin-bottom:1rem}.swagger-ui .mb4-m{margin-bottom:2rem}.swagger-ui .mb5-m{margin-bottom:4rem}.swagger-ui .mb6-m{margin-bottom:8rem}.swagger-ui .mb7-m{margin-bottom:16rem}.swagger-ui .mt0-m{margin-top:0}.swagger-ui .mt1-m{margin-top:.25rem}.swagger-ui .mt2-m{margin-top:.5rem}.swagger-ui .mt3-m{margin-top:1rem}.swagger-ui .mt4-m{margin-top:2rem}.swagger-ui .mt5-m{margin-top:4rem}.swagger-ui .mt6-m{margin-top:8rem}.swagger-ui .mt7-m{margin-top:16rem}.swagger-ui .mv0-m{margin-bottom:0;margin-top:0}.swagger-ui .mv1-m{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-m{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-m{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-m{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-m{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-m{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-m{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-m{margin-left:0;margin-right:0}.swagger-ui .mh1-m{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-m{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-m{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-m{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-m{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-m{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-m{margin-left:16rem;margin-right:16rem}}@media screen and (min-width:60em){.swagger-ui .pa0-l{padding:0}.swagger-ui .pa1-l{padding:.25rem}.swagger-ui .pa2-l{padding:.5rem}.swagger-ui .pa3-l{padding:1rem}.swagger-ui .pa4-l{padding:2rem}.swagger-ui .pa5-l{padding:4rem}.swagger-ui .pa6-l{padding:8rem}.swagger-ui .pa7-l{padding:16rem}.swagger-ui .pl0-l{padding-left:0}.swagger-ui .pl1-l{padding-left:.25rem}.swagger-ui .pl2-l{padding-left:.5rem}.swagger-ui .pl3-l{padding-left:1rem}.swagger-ui .pl4-l{padding-left:2rem}.swagger-ui .pl5-l{padding-left:4rem}.swagger-ui .pl6-l{padding-left:8rem}.swagger-ui .pl7-l{padding-left:16rem}.swagger-ui .pr0-l{padding-right:0}.swagger-ui .pr1-l{padding-right:.25rem}.swagger-ui .pr2-l{padding-right:.5rem}.swagger-ui .pr3-l{padding-right:1rem}.swagger-ui .pr4-l{padding-right:2rem}.swagger-ui .pr5-l{padding-right:4rem}.swagger-ui .pr6-l{padding-right:8rem}.swagger-ui .pr7-l{padding-right:16rem}.swagger-ui .pb0-l{padding-bottom:0}.swagger-ui .pb1-l{padding-bottom:.25rem}.swagger-ui .pb2-l{padding-bottom:.5rem}.swagger-ui .pb3-l{padding-bottom:1rem}.swagger-ui .pb4-l{padding-bottom:2rem}.swagger-ui .pb5-l{padding-bottom:4rem}.swagger-ui .pb6-l{padding-bottom:8rem}.swagger-ui .pb7-l{padding-bottom:16rem}.swagger-ui .pt0-l{padding-top:0}.swagger-ui .pt1-l{padding-top:.25rem}.swagger-ui .pt2-l{padding-top:.5rem}.swagger-ui .pt3-l{padding-top:1rem}.swagger-ui .pt4-l{padding-top:2rem}.swagger-ui .pt5-l{padding-top:4rem}.swagger-ui .pt6-l{padding-top:8rem}.swagger-ui .pt7-l{padding-top:16rem}.swagger-ui .pv0-l{padding-bottom:0;padding-top:0}.swagger-ui .pv1-l{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-l{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-l{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-l{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-l{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-l{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-l{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-l{padding-left:0;padding-right:0}.swagger-ui .ph1-l{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-l{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-l{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-l{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-l{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-l{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-l{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-l{margin:0}.swagger-ui .ma1-l{margin:.25rem}.swagger-ui .ma2-l{margin:.5rem}.swagger-ui .ma3-l{margin:1rem}.swagger-ui .ma4-l{margin:2rem}.swagger-ui .ma5-l{margin:4rem}.swagger-ui .ma6-l{margin:8rem}.swagger-ui .ma7-l{margin:16rem}.swagger-ui .ml0-l{margin-left:0}.swagger-ui .ml1-l{margin-left:.25rem}.swagger-ui .ml2-l{margin-left:.5rem}.swagger-ui .ml3-l{margin-left:1rem}.swagger-ui .ml4-l{margin-left:2rem}.swagger-ui .ml5-l{margin-left:4rem}.swagger-ui .ml6-l{margin-left:8rem}.swagger-ui .ml7-l{margin-left:16rem}.swagger-ui .mr0-l{margin-right:0}.swagger-ui .mr1-l{margin-right:.25rem}.swagger-ui .mr2-l{margin-right:.5rem}.swagger-ui .mr3-l{margin-right:1rem}.swagger-ui .mr4-l{margin-right:2rem}.swagger-ui .mr5-l{margin-right:4rem}.swagger-ui .mr6-l{margin-right:8rem}.swagger-ui .mr7-l{margin-right:16rem}.swagger-ui .mb0-l{margin-bottom:0}.swagger-ui .mb1-l{margin-bottom:.25rem}.swagger-ui .mb2-l{margin-bottom:.5rem}.swagger-ui .mb3-l{margin-bottom:1rem}.swagger-ui .mb4-l{margin-bottom:2rem}.swagger-ui .mb5-l{margin-bottom:4rem}.swagger-ui .mb6-l{margin-bottom:8rem}.swagger-ui .mb7-l{margin-bottom:16rem}.swagger-ui .mt0-l{margin-top:0}.swagger-ui .mt1-l{margin-top:.25rem}.swagger-ui .mt2-l{margin-top:.5rem}.swagger-ui .mt3-l{margin-top:1rem}.swagger-ui .mt4-l{margin-top:2rem}.swagger-ui .mt5-l{margin-top:4rem}.swagger-ui .mt6-l{margin-top:8rem}.swagger-ui .mt7-l{margin-top:16rem}.swagger-ui .mv0-l{margin-bottom:0;margin-top:0}.swagger-ui .mv1-l{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-l{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-l{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-l{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-l{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-l{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-l{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-l{margin-left:0;margin-right:0}.swagger-ui .mh1-l{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-l{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-l{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-l{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-l{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-l{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-l{margin-left:16rem;margin-right:16rem}}.swagger-ui .na1{margin:-.25rem}.swagger-ui .na2{margin:-.5rem}.swagger-ui .na3{margin:-1rem}.swagger-ui .na4{margin:-2rem}.swagger-ui .na5{margin:-4rem}.swagger-ui .na6{margin:-8rem}.swagger-ui .na7{margin:-16rem}.swagger-ui .nl1{margin-left:-.25rem}.swagger-ui .nl2{margin-left:-.5rem}.swagger-ui .nl3{margin-left:-1rem}.swagger-ui .nl4{margin-left:-2rem}.swagger-ui .nl5{margin-left:-4rem}.swagger-ui .nl6{margin-left:-8rem}.swagger-ui .nl7{margin-left:-16rem}.swagger-ui .nr1{margin-right:-.25rem}.swagger-ui .nr2{margin-right:-.5rem}.swagger-ui .nr3{margin-right:-1rem}.swagger-ui .nr4{margin-right:-2rem}.swagger-ui .nr5{margin-right:-4rem}.swagger-ui .nr6{margin-right:-8rem}.swagger-ui .nr7{margin-right:-16rem}.swagger-ui .nb1{margin-bottom:-.25rem}.swagger-ui .nb2{margin-bottom:-.5rem}.swagger-ui .nb3{margin-bottom:-1rem}.swagger-ui .nb4{margin-bottom:-2rem}.swagger-ui .nb5{margin-bottom:-4rem}.swagger-ui .nb6{margin-bottom:-8rem}.swagger-ui .nb7{margin-bottom:-16rem}.swagger-ui .nt1{margin-top:-.25rem}.swagger-ui .nt2{margin-top:-.5rem}.swagger-ui .nt3{margin-top:-1rem}.swagger-ui .nt4{margin-top:-2rem}.swagger-ui .nt5{margin-top:-4rem}.swagger-ui .nt6{margin-top:-8rem}.swagger-ui .nt7{margin-top:-16rem}@media screen and (min-width:30em){.swagger-ui .na1-ns{margin:-.25rem}.swagger-ui .na2-ns{margin:-.5rem}.swagger-ui .na3-ns{margin:-1rem}.swagger-ui .na4-ns{margin:-2rem}.swagger-ui .na5-ns{margin:-4rem}.swagger-ui .na6-ns{margin:-8rem}.swagger-ui .na7-ns{margin:-16rem}.swagger-ui .nl1-ns{margin-left:-.25rem}.swagger-ui .nl2-ns{margin-left:-.5rem}.swagger-ui .nl3-ns{margin-left:-1rem}.swagger-ui .nl4-ns{margin-left:-2rem}.swagger-ui .nl5-ns{margin-left:-4rem}.swagger-ui .nl6-ns{margin-left:-8rem}.swagger-ui .nl7-ns{margin-left:-16rem}.swagger-ui .nr1-ns{margin-right:-.25rem}.swagger-ui .nr2-ns{margin-right:-.5rem}.swagger-ui .nr3-ns{margin-right:-1rem}.swagger-ui .nr4-ns{margin-right:-2rem}.swagger-ui .nr5-ns{margin-right:-4rem}.swagger-ui .nr6-ns{margin-right:-8rem}.swagger-ui .nr7-ns{margin-right:-16rem}.swagger-ui .nb1-ns{margin-bottom:-.25rem}.swagger-ui .nb2-ns{margin-bottom:-.5rem}.swagger-ui .nb3-ns{margin-bottom:-1rem}.swagger-ui .nb4-ns{margin-bottom:-2rem}.swagger-ui .nb5-ns{margin-bottom:-4rem}.swagger-ui .nb6-ns{margin-bottom:-8rem}.swagger-ui .nb7-ns{margin-bottom:-16rem}.swagger-ui .nt1-ns{margin-top:-.25rem}.swagger-ui .nt2-ns{margin-top:-.5rem}.swagger-ui .nt3-ns{margin-top:-1rem}.swagger-ui .nt4-ns{margin-top:-2rem}.swagger-ui .nt5-ns{margin-top:-4rem}.swagger-ui .nt6-ns{margin-top:-8rem}.swagger-ui .nt7-ns{margin-top:-16rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .na1-m{margin:-.25rem}.swagger-ui .na2-m{margin:-.5rem}.swagger-ui .na3-m{margin:-1rem}.swagger-ui .na4-m{margin:-2rem}.swagger-ui .na5-m{margin:-4rem}.swagger-ui .na6-m{margin:-8rem}.swagger-ui .na7-m{margin:-16rem}.swagger-ui .nl1-m{margin-left:-.25rem}.swagger-ui .nl2-m{margin-left:-.5rem}.swagger-ui .nl3-m{margin-left:-1rem}.swagger-ui .nl4-m{margin-left:-2rem}.swagger-ui .nl5-m{margin-left:-4rem}.swagger-ui .nl6-m{margin-left:-8rem}.swagger-ui .nl7-m{margin-left:-16rem}.swagger-ui .nr1-m{margin-right:-.25rem}.swagger-ui .nr2-m{margin-right:-.5rem}.swagger-ui .nr3-m{margin-right:-1rem}.swagger-ui .nr4-m{margin-right:-2rem}.swagger-ui .nr5-m{margin-right:-4rem}.swagger-ui .nr6-m{margin-right:-8rem}.swagger-ui .nr7-m{margin-right:-16rem}.swagger-ui .nb1-m{margin-bottom:-.25rem}.swagger-ui .nb2-m{margin-bottom:-.5rem}.swagger-ui .nb3-m{margin-bottom:-1rem}.swagger-ui .nb4-m{margin-bottom:-2rem}.swagger-ui .nb5-m{margin-bottom:-4rem}.swagger-ui .nb6-m{margin-bottom:-8rem}.swagger-ui .nb7-m{margin-bottom:-16rem}.swagger-ui .nt1-m{margin-top:-.25rem}.swagger-ui .nt2-m{margin-top:-.5rem}.swagger-ui .nt3-m{margin-top:-1rem}.swagger-ui .nt4-m{margin-top:-2rem}.swagger-ui .nt5-m{margin-top:-4rem}.swagger-ui .nt6-m{margin-top:-8rem}.swagger-ui .nt7-m{margin-top:-16rem}}@media screen and (min-width:60em){.swagger-ui .na1-l{margin:-.25rem}.swagger-ui .na2-l{margin:-.5rem}.swagger-ui .na3-l{margin:-1rem}.swagger-ui .na4-l{margin:-2rem}.swagger-ui .na5-l{margin:-4rem}.swagger-ui .na6-l{margin:-8rem}.swagger-ui .na7-l{margin:-16rem}.swagger-ui .nl1-l{margin-left:-.25rem}.swagger-ui .nl2-l{margin-left:-.5rem}.swagger-ui .nl3-l{margin-left:-1rem}.swagger-ui .nl4-l{margin-left:-2rem}.swagger-ui .nl5-l{margin-left:-4rem}.swagger-ui .nl6-l{margin-left:-8rem}.swagger-ui .nl7-l{margin-left:-16rem}.swagger-ui .nr1-l{margin-right:-.25rem}.swagger-ui .nr2-l{margin-right:-.5rem}.swagger-ui .nr3-l{margin-right:-1rem}.swagger-ui .nr4-l{margin-right:-2rem}.swagger-ui .nr5-l{margin-right:-4rem}.swagger-ui .nr6-l{margin-right:-8rem}.swagger-ui .nr7-l{margin-right:-16rem}.swagger-ui .nb1-l{margin-bottom:-.25rem}.swagger-ui .nb2-l{margin-bottom:-.5rem}.swagger-ui .nb3-l{margin-bottom:-1rem}.swagger-ui .nb4-l{margin-bottom:-2rem}.swagger-ui .nb5-l{margin-bottom:-4rem}.swagger-ui .nb6-l{margin-bottom:-8rem}.swagger-ui .nb7-l{margin-bottom:-16rem}.swagger-ui .nt1-l{margin-top:-.25rem}.swagger-ui .nt2-l{margin-top:-.5rem}.swagger-ui .nt3-l{margin-top:-1rem}.swagger-ui .nt4-l{margin-top:-2rem}.swagger-ui .nt5-l{margin-top:-4rem}.swagger-ui .nt6-l{margin-top:-8rem}.swagger-ui .nt7-l{margin-top:-16rem}}.swagger-ui .collapse{border-collapse:collapse;border-spacing:0}.swagger-ui .striped--light-silver:nth-child(odd){background-color:#aaa}.swagger-ui .striped--moon-gray:nth-child(odd){background-color:#ccc}.swagger-ui .striped--light-gray:nth-child(odd){background-color:#eee}.swagger-ui .striped--near-white:nth-child(odd){background-color:#f4f4f4}.swagger-ui .stripe-light:nth-child(odd){background-color:hsla(0,0%,100%,.1)}.swagger-ui .stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.swagger-ui .strike{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline{-webkit-text-decoration:none;text-decoration:none}@media screen and (min-width:30em){.swagger-ui .strike-ns{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-ns{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-ns{-webkit-text-decoration:none;text-decoration:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .strike-m{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-m{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-m{-webkit-text-decoration:none;text-decoration:none}}@media screen and (min-width:60em){.swagger-ui .strike-l{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-l{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-l{-webkit-text-decoration:none;text-decoration:none}}.swagger-ui .tl{text-align:left}.swagger-ui .tr{text-align:right}.swagger-ui .tc{text-align:center}.swagger-ui .tj{text-align:justify}@media screen and (min-width:30em){.swagger-ui .tl-ns{text-align:left}.swagger-ui .tr-ns{text-align:right}.swagger-ui .tc-ns{text-align:center}.swagger-ui .tj-ns{text-align:justify}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .tl-m{text-align:left}.swagger-ui .tr-m{text-align:right}.swagger-ui .tc-m{text-align:center}.swagger-ui .tj-m{text-align:justify}}@media screen and (min-width:60em){.swagger-ui .tl-l{text-align:left}.swagger-ui .tr-l{text-align:right}.swagger-ui .tc-l{text-align:center}.swagger-ui .tj-l{text-align:justify}}.swagger-ui .ttc{text-transform:capitalize}.swagger-ui .ttl{text-transform:lowercase}.swagger-ui .ttu{text-transform:uppercase}.swagger-ui .ttn{text-transform:none}@media screen and (min-width:30em){.swagger-ui .ttc-ns{text-transform:capitalize}.swagger-ui .ttl-ns{text-transform:lowercase}.swagger-ui .ttu-ns{text-transform:uppercase}.swagger-ui .ttn-ns{text-transform:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ttc-m{text-transform:capitalize}.swagger-ui .ttl-m{text-transform:lowercase}.swagger-ui .ttu-m{text-transform:uppercase}.swagger-ui .ttn-m{text-transform:none}}@media screen and (min-width:60em){.swagger-ui .ttc-l{text-transform:capitalize}.swagger-ui .ttl-l{text-transform:lowercase}.swagger-ui .ttu-l{text-transform:uppercase}.swagger-ui .ttn-l{text-transform:none}}.swagger-ui .f-6,.swagger-ui .f-headline{font-size:6rem}.swagger-ui .f-5,.swagger-ui .f-subheadline{font-size:5rem}.swagger-ui .f1{font-size:3rem}.swagger-ui .f2{font-size:2.25rem}.swagger-ui .f3{font-size:1.5rem}.swagger-ui .f4{font-size:1.25rem}.swagger-ui .f5{font-size:1rem}.swagger-ui .f6{font-size:.875rem}.swagger-ui .f7{font-size:.75rem}@media screen and (min-width:30em){.swagger-ui .f-6-ns,.swagger-ui .f-headline-ns{font-size:6rem}.swagger-ui .f-5-ns,.swagger-ui .f-subheadline-ns{font-size:5rem}.swagger-ui .f1-ns{font-size:3rem}.swagger-ui .f2-ns{font-size:2.25rem}.swagger-ui .f3-ns{font-size:1.5rem}.swagger-ui .f4-ns{font-size:1.25rem}.swagger-ui .f5-ns{font-size:1rem}.swagger-ui .f6-ns{font-size:.875rem}.swagger-ui .f7-ns{font-size:.75rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .f-6-m,.swagger-ui .f-headline-m{font-size:6rem}.swagger-ui .f-5-m,.swagger-ui .f-subheadline-m{font-size:5rem}.swagger-ui .f1-m{font-size:3rem}.swagger-ui .f2-m{font-size:2.25rem}.swagger-ui .f3-m{font-size:1.5rem}.swagger-ui .f4-m{font-size:1.25rem}.swagger-ui .f5-m{font-size:1rem}.swagger-ui .f6-m{font-size:.875rem}.swagger-ui .f7-m{font-size:.75rem}}@media screen and (min-width:60em){.swagger-ui .f-6-l,.swagger-ui .f-headline-l{font-size:6rem}.swagger-ui .f-5-l,.swagger-ui .f-subheadline-l{font-size:5rem}.swagger-ui .f1-l{font-size:3rem}.swagger-ui .f2-l{font-size:2.25rem}.swagger-ui .f3-l{font-size:1.5rem}.swagger-ui .f4-l{font-size:1.25rem}.swagger-ui .f5-l{font-size:1rem}.swagger-ui .f6-l{font-size:.875rem}.swagger-ui .f7-l{font-size:.75rem}}.swagger-ui .measure{max-width:30em}.swagger-ui .measure-wide{max-width:34em}.swagger-ui .measure-narrow{max-width:20em}.swagger-ui .indent{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media screen and (min-width:30em){.swagger-ui .measure-ns{max-width:30em}.swagger-ui .measure-wide-ns{max-width:34em}.swagger-ui .measure-narrow-ns{max-width:20em}.swagger-ui .indent-ns{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-ns{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-ns{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .measure-m{max-width:30em}.swagger-ui .measure-wide-m{max-width:34em}.swagger-ui .measure-narrow-m{max-width:20em}.swagger-ui .indent-m{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-m{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-m{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media screen and (min-width:60em){.swagger-ui .measure-l{max-width:30em}.swagger-ui .measure-wide-l{max-width:34em}.swagger-ui .measure-narrow-l{max-width:20em}.swagger-ui .indent-l{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-l{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-l{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}.swagger-ui .overflow-container{overflow-y:scroll}.swagger-ui .center{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto{margin-right:auto}.swagger-ui .ml-auto{margin-left:auto}@media screen and (min-width:30em){.swagger-ui .center-ns{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-ns{margin-right:auto}.swagger-ui .ml-auto-ns{margin-left:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .center-m{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-m{margin-right:auto}.swagger-ui .ml-auto-m{margin-left:auto}}@media screen and (min-width:60em){.swagger-ui .center-l{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-l{margin-right:auto}.swagger-ui .ml-auto-l{margin-left:auto}}.swagger-ui .clip{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}@media screen and (min-width:30em){.swagger-ui .clip-ns{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .clip-m{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}@media screen and (min-width:60em){.swagger-ui .clip-l{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}.swagger-ui .ws-normal{white-space:normal}.swagger-ui .nowrap{white-space:nowrap}.swagger-ui .pre{white-space:pre}@media screen and (min-width:30em){.swagger-ui .ws-normal-ns{white-space:normal}.swagger-ui .nowrap-ns{white-space:nowrap}.swagger-ui .pre-ns{white-space:pre}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ws-normal-m{white-space:normal}.swagger-ui .nowrap-m{white-space:nowrap}.swagger-ui .pre-m{white-space:pre}}@media screen and (min-width:60em){.swagger-ui .ws-normal-l{white-space:normal}.swagger-ui .nowrap-l{white-space:nowrap}.swagger-ui .pre-l{white-space:pre}}.swagger-ui .v-base{vertical-align:baseline}.swagger-ui .v-mid{vertical-align:middle}.swagger-ui .v-top{vertical-align:top}.swagger-ui .v-btm{vertical-align:bottom}@media screen and (min-width:30em){.swagger-ui .v-base-ns{vertical-align:baseline}.swagger-ui .v-mid-ns{vertical-align:middle}.swagger-ui .v-top-ns{vertical-align:top}.swagger-ui .v-btm-ns{vertical-align:bottom}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .v-base-m{vertical-align:baseline}.swagger-ui .v-mid-m{vertical-align:middle}.swagger-ui .v-top-m{vertical-align:top}.swagger-ui .v-btm-m{vertical-align:bottom}}@media screen and (min-width:60em){.swagger-ui .v-base-l{vertical-align:baseline}.swagger-ui .v-mid-l{vertical-align:middle}.swagger-ui .v-top-l{vertical-align:top}.swagger-ui .v-btm-l{vertical-align:bottom}}.swagger-ui .dim{opacity:1;transition:opacity .15s ease-in}.swagger-ui .dim:focus,.swagger-ui .dim:hover{opacity:.5;transition:opacity .15s ease-in}.swagger-ui .dim:active{opacity:.8;transition:opacity .15s ease-out}.swagger-ui .glow{transition:opacity .15s ease-in}.swagger-ui .glow:focus,.swagger-ui .glow:hover{opacity:1;transition:opacity .15s ease-in}.swagger-ui .hide-child .child{opacity:0;transition:opacity .15s ease-in}.swagger-ui .hide-child:active .child,.swagger-ui .hide-child:focus .child,.swagger-ui .hide-child:hover .child{opacity:1;transition:opacity .15s ease-in}.swagger-ui .underline-hover:focus,.swagger-ui .underline-hover:hover{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .grow{-moz-osx-font-smoothing:grayscale;backface-visibility:hidden;transform:translateZ(0);transition:transform .25s ease-out}.swagger-ui .grow:focus,.swagger-ui .grow:hover{transform:scale(1.05)}.swagger-ui .grow:active{transform:scale(.9)}.swagger-ui .grow-large{-moz-osx-font-smoothing:grayscale;backface-visibility:hidden;transform:translateZ(0);transition:transform .25s ease-in-out}.swagger-ui .grow-large:focus,.swagger-ui .grow-large:hover{transform:scale(1.2)}.swagger-ui .grow-large:active{transform:scale(.95)}.swagger-ui .pointer:hover{cursor:pointer}.swagger-ui .shadow-hover{cursor:pointer;position:relative;transition:all .5s cubic-bezier(.165,.84,.44,1)}.swagger-ui .shadow-hover:after{border-radius:inherit;box-shadow:0 0 16px 2px rgba(0,0,0,.2);content:"";height:100%;left:0;opacity:0;position:absolute;top:0;transition:opacity .5s cubic-bezier(.165,.84,.44,1);width:100%;z-index:-1}.swagger-ui .shadow-hover:focus:after,.swagger-ui .shadow-hover:hover:after{opacity:1}.swagger-ui .bg-animate,.swagger-ui .bg-animate:focus,.swagger-ui .bg-animate:hover{transition:background-color .15s ease-in-out}.swagger-ui .z-0{z-index:0}.swagger-ui .z-1{z-index:1}.swagger-ui .z-2{z-index:2}.swagger-ui .z-3{z-index:3}.swagger-ui .z-4{z-index:4}.swagger-ui .z-5{z-index:5}.swagger-ui .z-999{z-index:999}.swagger-ui .z-9999{z-index:9999}.swagger-ui .z-max{z-index:2147483647}.swagger-ui .z-inherit{z-index:inherit}.swagger-ui .z-initial,.swagger-ui .z-unset{z-index:auto}.swagger-ui .nested-copy-line-height ol,.swagger-ui .nested-copy-line-height p,.swagger-ui .nested-copy-line-height ul{line-height:1.5}.swagger-ui .nested-headline-line-height h1,.swagger-ui .nested-headline-line-height h2,.swagger-ui .nested-headline-line-height h3,.swagger-ui .nested-headline-line-height h4,.swagger-ui .nested-headline-line-height h5,.swagger-ui .nested-headline-line-height h6{line-height:1.25}.swagger-ui .nested-list-reset ol,.swagger-ui .nested-list-reset ul{list-style-type:none;margin-left:0;padding-left:0}.swagger-ui .nested-copy-indent p+p{margin-bottom:0;margin-top:0;text-indent:.1em}.swagger-ui .nested-copy-seperator p+p{margin-top:1.5em}.swagger-ui .nested-img img{display:block;max-width:100%;width:100%}.swagger-ui .nested-links a{color:#357edd;transition:color .15s ease-in}.swagger-ui .nested-links a:focus,.swagger-ui .nested-links a:hover{color:#96ccff;transition:color .15s ease-in}.swagger-ui .wrapper{box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column}.swagger-ui .try-out.btn-group{display:flex;flex:.1 2 auto;padding:0}.swagger-ui .try-out__btn{margin-left:1.25rem}.swagger-ui .opblock-tag{align-items:center;border-bottom:1px solid rgba(59,65,81,.3);cursor:pointer;display:flex;padding:10px 20px 10px 10px;transition:all .2s}.swagger-ui .opblock-tag:hover{background:rgba(0,0,0,.02)}.swagger-ui .opblock-tag{color:#3b4151;font-family:sans-serif;font-size:24px;margin:0 0 5px}.swagger-ui .opblock-tag.no-desc span{flex:1}.swagger-ui .opblock-tag svg{transition:all .4s}.swagger-ui .opblock-tag small{color:#3b4151;flex:2;font-family:sans-serif;font-size:14px;font-weight:400;padding:0 10px}.swagger-ui .opblock-tag>div{flex:1 1 150px;font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:640px){.swagger-ui .opblock-tag small,.swagger-ui .opblock-tag>div{flex:1}}.swagger-ui .opblock-tag .info__externaldocs{text-align:right}.swagger-ui .parameter__type{color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;padding:5px 0}.swagger-ui .parameter-controls{margin-top:.75em}.swagger-ui .examples__title{display:block;font-size:1.1em;font-weight:700;margin-bottom:.75em}.swagger-ui .examples__section{margin-top:1.5em}.swagger-ui .examples__section-header{font-size:.9rem;font-weight:700;margin-bottom:.5rem}.swagger-ui .examples-select{display:inline-block;margin-bottom:.75em}.swagger-ui .examples-select .examples-select-element{width:100%}.swagger-ui .examples-select__section-label{font-size:.9rem;font-weight:700;margin-right:.5rem}.swagger-ui .example__section{margin-top:1.5em}.swagger-ui .example__section-header{font-size:.9rem;font-weight:700;margin-bottom:.5rem}.swagger-ui .view-line-link{cursor:pointer;margin:0 5px;position:relative;top:3px;transition:all .5s;width:20px}.swagger-ui .opblock{border:1px solid #000;border-radius:4px;box-shadow:0 0 3px rgba(0,0,0,.19);margin:0 0 15px}.swagger-ui .opblock .tab-header{display:flex;flex:1}.swagger-ui .opblock .tab-header .tab-item{cursor:pointer;padding:0 40px}.swagger-ui .opblock .tab-header .tab-item:first-of-type{padding:0 40px 0 0}.swagger-ui .opblock .tab-header .tab-item.active h4 span{position:relative}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:grey;bottom:-15px;content:"";height:4px;left:50%;position:absolute;transform:translateX(-50%);width:120%}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid #000}.swagger-ui .opblock .opblock-section-header{align-items:center;background:hsla(0,0%,100%,.8);box-shadow:0 1px 2px rgba(0,0,0,.1);display:flex;min-height:50px;padding:8px 20px}.swagger-ui .opblock .opblock-section-header>label{align-items:center;color:#3b4151;display:flex;font-family:sans-serif;font-size:12px;font-weight:700;margin:0 0 0 auto}.swagger-ui .opblock .opblock-section-header>label>span{padding:0 10px 0 0}.swagger-ui .opblock .opblock-section-header h4{color:#3b4151;flex:1;font-family:sans-serif;font-size:14px;margin:0}.swagger-ui .opblock .opblock-summary-method{background:#000;border-radius:3px;color:#fff;font-family:sans-serif;font-size:14px;font-weight:700;min-width:80px;padding:6px 0;text-align:center;text-shadow:0 1px 0 rgba(0,0,0,.1)}@media(max-width:768px){.swagger-ui .opblock .opblock-summary-method{font-size:12px}}.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{align-items:center;color:#3b4151;display:flex;font-family:monospace;font-size:16px;font-weight:600;word-break:break-word}@media(max-width:768px){.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{font-size:12px}}.swagger-ui .opblock .opblock-summary-path{flex-shrink:1}@media(max-width:640px){.swagger-ui .opblock .opblock-summary-path{max-width:100%}}.swagger-ui .opblock .opblock-summary-path__deprecated{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .opblock .opblock-summary-operation-id{font-size:14px}.swagger-ui .opblock .opblock-summary-description{color:#3b4151;font-family:sans-serif;font-size:13px;word-break:break-word}.swagger-ui .opblock .opblock-summary-path-description-wrapper{align-items:center;display:flex;flex-direction:row;flex-grow:1;flex-wrap:wrap;gap:0 10px;padding:0 10px}@media(max-width:550px){.swagger-ui .opblock .opblock-summary-path-description-wrapper{align-items:flex-start;flex-direction:column}}.swagger-ui .opblock .opblock-summary{align-items:center;cursor:pointer;display:flex;padding:5px}.swagger-ui .opblock .opblock-summary .view-line-link{cursor:pointer;margin:0;position:relative;top:2px;transition:all .5s;width:0}.swagger-ui .opblock .opblock-summary:hover .view-line-link{margin:0 5px;width:18px}.swagger-ui .opblock .opblock-summary:hover .view-line-link.copy-to-clipboard{width:24px}.swagger-ui .opblock.opblock-post{background:rgba(73,204,144,.1);border-color:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:#49cc90}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:#49cc90}.swagger-ui .opblock.opblock-put{background:rgba(252,161,48,.1);border-color:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:#fca130}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:#fca130}.swagger-ui .opblock.opblock-delete{background:rgba(249,62,62,.1);border-color:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:#f93e3e}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:#f93e3e}.swagger-ui .opblock.opblock-get{background:rgba(97,175,254,.1);border-color:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:#61affe}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:#61affe}.swagger-ui .opblock.opblock-patch{background:rgba(80,227,194,.1);border-color:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:#50e3c2}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:#50e3c2}.swagger-ui .opblock.opblock-head{background:rgba(144,18,254,.1);border-color:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:#9012fe}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:#9012fe}.swagger-ui .opblock.opblock-options{background:rgba(13,90,167,.1);border-color:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:#0d5aa7}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:#0d5aa7}.swagger-ui .opblock.opblock-deprecated{background:hsla(0,0%,92%,.1);border-color:#ebebeb;opacity:.6}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:#ebebeb}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:#ebebeb}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:#ebebeb}.swagger-ui .opblock .opblock-schemes{padding:8px 20px}.swagger-ui .opblock .opblock-schemes .schemes-title{padding:0 10px 0 0}.swagger-ui .filter .operation-filter-input{border:2px solid #d8dde7;margin:20px 0;padding:10px;width:100%}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:red}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:#aaa}.swagger-ui .model-example{margin-top:1em}.swagger-ui .model-example .model-container{overflow-x:auto;width:100%}.swagger-ui .model-example .model-container .model-hint:not(.model-hint--embedded){top:-1.15em}.swagger-ui .tab{display:flex;list-style:none;padding:0}.swagger-ui .tab li{color:#3b4151;cursor:pointer;font-family:sans-serif;font-size:12px;min-width:60px;padding:0}.swagger-ui .tab li:first-of-type{padding-left:0;padding-right:12px;position:relative}.swagger-ui .tab li:first-of-type:after{background:rgba(0,0,0,.2);content:"";height:100%;position:absolute;right:6px;top:0;width:1px}.swagger-ui .tab li.active{font-weight:700}.swagger-ui .tab li button.tablinks{background:none;border:0;color:inherit;font-family:inherit;font-weight:inherit;padding:0}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-title_normal{color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px;padding:15px 20px}.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-title_normal h4{color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px}.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal p{color:#3b4151;font-family:sans-serif;font-size:14px;margin:0}.swagger-ui .opblock-external-docs-wrapper h4{padding-left:0}.swagger-ui .execute-wrapper{padding:20px;text-align:right}.swagger-ui .execute-wrapper .btn{padding:8px 40px;width:100%}.swagger-ui .body-param-options{display:flex;flex-direction:column}.swagger-ui .body-param-options .body-param-edit{padding:10px 0}.swagger-ui .body-param-options label{padding:8px 0}.swagger-ui .body-param-options label select{margin:3px 0 0}.swagger-ui .responses-inner{padding:20px}.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:#3b4151;font-family:sans-serif;font-size:12px;margin:10px 0 5px}.swagger-ui .responses-inner .curl{max-height:400px;min-height:6em;overflow-y:auto}.swagger-ui .response-col_status{color:#3b4151;font-family:sans-serif;font-size:14px}.swagger-ui .response-col_status .response-undocumented{color:#909090;font-family:monospace;font-size:11px;font-weight:600}.swagger-ui .response-col_links{color:#3b4151;font-family:sans-serif;font-size:14px;max-width:40em;padding-left:2em}.swagger-ui .response-col_links .response-undocumented{color:#909090;font-family:monospace;font-size:11px;font-weight:600}.swagger-ui .response-col_links .operation-link{margin-bottom:1.5em}.swagger-ui .response-col_links .operation-link .description{margin-bottom:.5em}.swagger-ui .opblock-body .opblock-loading-animation{display:block;margin:3em auto}.swagger-ui .opblock-body pre.microlight{background:#333;border-radius:4px;font-size:12px;hyphens:auto;margin:0;padding:10px;white-space:pre-wrap;word-break:break-all;word-break:break-word;word-wrap:break-word;color:#fff;font-family:monospace;font-weight:600}.swagger-ui .opblock-body pre.microlight .headerline{display:block}.swagger-ui .highlight-code{position:relative}.swagger-ui .highlight-code>.microlight{max-height:400px;min-height:6em;overflow-y:auto}.swagger-ui .highlight-code>.microlight code{white-space:pre-wrap!important;word-break:break-all}.swagger-ui .curl-command{position:relative}.swagger-ui .download-contents{align-items:center;background:#7d8293;border:none;border-radius:4px;bottom:10px;color:#fff;display:flex;font-family:sans-serif;font-size:14px;font-weight:600;height:30px;justify-content:center;padding:5px;position:absolute;right:10px;text-align:center}.swagger-ui .scheme-container{background:#fff;box-shadow:0 1px 2px 0 rgba(0,0,0,.15);margin:0 0 20px;padding:30px 0}.swagger-ui .scheme-container .schemes{align-items:flex-end;display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between}.swagger-ui .scheme-container .schemes>.schemes-server-container{display:flex;flex-wrap:wrap;gap:10px}.swagger-ui .scheme-container .schemes>.schemes-server-container>label{color:#3b4151;display:flex;flex-direction:column;font-family:sans-serif;font-size:12px;font-weight:700;margin:-20px 15px 0 0}.swagger-ui .scheme-container .schemes>.schemes-server-container>label select{min-width:130px;text-transform:uppercase}.swagger-ui .scheme-container .schemes:not(:has(.schemes-server-container)){justify-content:flex-end}.swagger-ui .scheme-container .schemes .auth-wrapper{flex:none;justify-content:start}.swagger-ui .scheme-container .schemes .auth-wrapper .authorize{display:flex;flex-wrap:nowrap;margin:0;padding-right:20px}.swagger-ui .loading-container{align-items:center;display:flex;flex-direction:column;justify-content:center;margin-top:1em;min-height:1px;padding:40px 0 60px}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{color:#3b4151;content:"loading";font-family:sans-serif;font-size:10px;font-weight:700;left:50%;position:absolute;text-transform:uppercase;top:50%;transform:translate(-50%,-50%)}.swagger-ui .loading-container .loading:before{animation:rotation 1s linear infinite,opacity .5s;backface-visibility:hidden;border:2px solid rgba(85,85,85,.1);border-radius:100%;border-top-color:rgba(0,0,0,.6);content:"";display:block;height:60px;left:50%;margin:-30px;opacity:1;position:absolute;top:50%;width:60px}@keyframes rotation{to{transform:rotate(1turn)}}.swagger-ui .response-controls{display:flex;padding-top:1em}.swagger-ui .response-control-media-type{margin-right:1em}.swagger-ui .response-control-media-type--accept-controller select{border-color:green}.swagger-ui .response-control-media-type__accept-message{color:green;font-size:.7em}.swagger-ui .response-control-examples__title,.swagger-ui .response-control-media-type__title{display:block;font-size:.7em;margin-bottom:.2em}@keyframes blinker{50%{opacity:0}}.swagger-ui .hidden{display:none}.swagger-ui .no-margin{border:none;height:auto;margin:0;padding:0}.swagger-ui .float-right{float:right}.swagger-ui .svg-assets{height:0;position:absolute;width:0}.swagger-ui section h3{color:#3b4151;font-family:sans-serif}.swagger-ui a.nostyle{display:inline}.swagger-ui a.nostyle,.swagger-ui a.nostyle:visited{color:inherit;cursor:pointer;text-decoration:inherit}.swagger-ui .fallback{color:#aaa;padding:1em}.swagger-ui .version-pragma{height:100%;padding:5em 0}.swagger-ui .version-pragma__message{display:flex;font-size:1.2em;height:100%;justify-content:center;line-height:1.5em;padding:0 .6em;text-align:center}.swagger-ui .version-pragma__message>div{flex:1;max-width:55ch}.swagger-ui .version-pragma__message code{background-color:#dedede;padding:4px 4px 2px;white-space:pre}.swagger-ui .opblock-link{font-weight:400}.swagger-ui .opblock-link.shown{font-weight:700}.swagger-ui span.token-string{color:#555}.swagger-ui span.token-not-formatted{color:#555;font-weight:700}.swagger-ui .btn{background:transparent;border:2px solid grey;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.1);color:#3b4151;font-family:sans-serif;font-size:14px;font-weight:700;padding:5px 23px;transition:all .3s}.swagger-ui .btn.btn-sm{font-size:12px;padding:4px 23px}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{background-color:transparent;border-color:#ff6060;color:#ff6060;font-family:sans-serif}.swagger-ui .btn.authorize{background-color:transparent;border-color:#49cc90;color:#49cc90;display:inline;line-height:1}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{background-color:#4990e2;border-color:#4990e2;color:#fff}.swagger-ui .btn-group{display:flex;padding:30px}.swagger-ui .btn-group .btn{flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{background:none;border:none;padding:0 0 0 10px}.swagger-ui .authorization__btn .locked{opacity:1}.swagger-ui .authorization__btn .unlocked{opacity:.4}.swagger-ui .model-box-control,.swagger-ui .models-control,.swagger-ui .opblock-summary-control{all:inherit;border-bottom:0;cursor:pointer;flex:1;padding:0}.swagger-ui .model-box-control:focus,.swagger-ui .models-control:focus,.swagger-ui .opblock-summary-control:focus{outline:auto}.swagger-ui .expand-methods,.swagger-ui .expand-operation{background:none;border:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{height:20px;width:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#404040}.swagger-ui .expand-methods svg{transition:all .3s;fill:#707070}.swagger-ui button{cursor:pointer}.swagger-ui button.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui .copy-to-clipboard{align-items:center;background:#7d8293;border:none;border-radius:4px;bottom:10px;display:flex;height:30px;justify-content:center;position:absolute;right:100px;width:30px}.swagger-ui .copy-to-clipboard button{background:url("data:image/svg+xml;charset=utf-8,") 50% no-repeat;border:none;flex-grow:1;flex-shrink:1;height:25px}.swagger-ui .copy-to-clipboard:active{background:#5e626f}.swagger-ui .opblock-control-arrow{background:none;border:none;text-align:center}.swagger-ui .curl-command .copy-to-clipboard{bottom:5px;height:20px;right:10px;width:20px}.swagger-ui .curl-command .copy-to-clipboard button{height:18px}.swagger-ui .opblock .opblock-summary .view-line-link.copy-to-clipboard{height:26px;position:static}.swagger-ui select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f7f7 url("data:image/svg+xml;charset=utf-8,") right 10px center no-repeat;background-size:20px;border:2px solid #41444e;border-radius:4px;box-shadow:0 1px 2px 0 rgba(0,0,0,.25);color:#3b4151;font-family:sans-serif;font-size:14px;font-weight:700;padding:5px 40px 5px 10px}.swagger-ui select[multiple]{background:#f7f7f7;margin:5px 0;padding:5px}.swagger-ui select.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui .opblock-body select{min-width:230px}@media(max-width:768px){.swagger-ui .opblock-body select{min-width:180px}}@media(max-width:640px){.swagger-ui .opblock-body select{min-width:100%;width:100%}}.swagger-ui label{color:#3b4151;font-family:sans-serif;font-size:12px;font-weight:700;margin:0 0 5px}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{line-height:1}@media(max-width:768px){.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{max-width:175px}}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:#fff;border:1px solid #d9d9d9;border-radius:4px;margin:5px 0;min-width:100px;padding:8px 10px}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui textarea.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui input[disabled],.swagger-ui select[disabled],.swagger-ui textarea[disabled]{background-color:#fafafa;color:#888;cursor:not-allowed}.swagger-ui select[disabled]{border-color:#888}.swagger-ui textarea[disabled]{background-color:#41444e;color:#fff}@keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}.swagger-ui textarea{background:hsla(0,0%,100%,.8);border:none;border-radius:4px;color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;min-height:280px;outline:none;padding:10px;width:100%}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{background:#41444e;border-radius:4px;color:#fff;font-family:monospace;font-size:12px;font-weight:600;margin:0;min-height:100px;padding:10px;resize:none}.swagger-ui .checkbox{color:#303030;padding:5px 0 10px;transition:opacity .5s}.swagger-ui .checkbox label{display:flex}.swagger-ui .checkbox p{color:#3b4151;font-family:monospace;font-style:italic;font-weight:400!important;font-weight:600;margin:0!important}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:#e8e8e8;border-radius:1px;box-shadow:0 0 0 2px #e8e8e8;cursor:pointer;display:inline-block;flex:none;height:16px;margin:0 8px 0 0;padding:5px;position:relative;top:3px;width:16px}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url("data:image/svg+xml;charset=utf-8,") 50% no-repeat}.swagger-ui .dialog-ux{bottom:0;left:0;position:fixed;right:0;top:0;z-index:9999}.swagger-ui .dialog-ux .backdrop-ux{background:rgba(0,0,0,.8);bottom:0;left:0;position:fixed;right:0;top:0}.swagger-ui .dialog-ux .modal-ux{background:#fff;border:1px solid #ebebeb;border-radius:4px;box-shadow:0 10px 30px 0 rgba(0,0,0,.2);left:50%;max-width:650px;min-width:300px;position:absolute;top:50%;transform:translate(-50%,-50%);width:100%;z-index:9999}.swagger-ui .dialog-ux .modal-ux-content{max-height:540px;overflow-y:auto;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{color:#41444e;color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px}.swagger-ui .dialog-ux .modal-ux-content h4{color:#3b4151;font-family:sans-serif;font-size:18px;font-weight:600;margin:15px 0 0}.swagger-ui .dialog-ux .modal-ux-header{align-items:center;border-bottom:1px solid #ebebeb;display:flex;padding:12px 0}.swagger-ui .dialog-ux .modal-ux-header .close-modal{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:none;padding:0 10px}.swagger-ui .dialog-ux .modal-ux-header h3{color:#3b4151;flex:1;font-family:sans-serif;font-size:20px;font-weight:600;margin:0;padding:0 20px}.swagger-ui .model{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300;font-weight:600}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:#a0a0a0!important}.swagger-ui .model .deprecated>td:first-of-type{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .model-toggle{cursor:pointer;display:inline-block;font-size:10px;margin:auto .3em;position:relative;top:6px;transform:rotate(90deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .model-toggle.collapsed{transform:rotate(0deg)}.swagger-ui .model-toggle:after{background:url("data:image/svg+xml;charset=utf-8,") 50% no-repeat;background-size:100%;content:"";display:block;height:20px;width:20px}.swagger-ui .model-jump-to-path{cursor:pointer;position:relative}.swagger-ui .model-jump-to-path .view-line-link{cursor:pointer;position:absolute;top:-.4em}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{display:block}.swagger-ui .model-hint{background:rgba(0,0,0,.7);border-radius:4px;color:#ebebeb;display:none;padding:.1em .5em;position:absolute;top:-1.8em;white-space:nowrap}.swagger-ui .model p{margin:0 0 1em}.swagger-ui .model .property{color:#999;font-style:italic}.swagger-ui .model .property.primitive{color:#6b6b6b}.swagger-ui .model .property.primitive.extension{display:block}.swagger-ui .model .property.primitive.extension>td:first-child{padding-left:0;padding-right:0;width:auto}.swagger-ui .model .property.primitive.extension>td:first-child:after{content:": "}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:#666;font-weight:400}.swagger-ui table.model tr.description td:first-child,.swagger-ui table.model tr.property-row.required td:first-child{font-weight:700}.swagger-ui table.model tr.property-row td{vertical-align:top}.swagger-ui table.model tr.property-row td:first-child{padding-right:.2em}.swagger-ui table.model tr.property-row .star{color:red}.swagger-ui table.model tr.extension{color:#777}.swagger-ui table.model tr.extension td:last-child{vertical-align:top}.swagger-ui table.model tr.external-docs td:first-child{font-weight:700}.swagger-ui table.model tr .renderedMarkdown p:first-child{margin-top:0}.swagger-ui section.models{border:1px solid rgba(59,65,81,.3);border-radius:4px;margin:30px 0}.swagger-ui section.models .pointer{cursor:pointer}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{border-bottom:1px solid rgba(59,65,81,.3);margin:0 0 5px}.swagger-ui section.models h4{align-items:center;color:#606060;cursor:pointer;display:flex;font-family:sans-serif;font-size:16px;margin:0;padding:10px 20px 10px 10px;transition:all .2s}.swagger-ui section.models h4 svg{transition:all .4s}.swagger-ui section.models h4 span{flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{color:#707070;font-family:sans-serif;font-size:16px;margin:0 0 10px}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{background:rgba(0,0,0,.05);border-radius:4px;margin:0 20px 15px;position:relative;transition:all .5s}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-container .models-jump-to-path{opacity:.65;position:absolute;right:5px;top:8px}.swagger-ui section.models .model-box{background:none}.swagger-ui section.models .model-box:has(.model-box){overflow-x:auto;width:100%}.swagger-ui .model-box{background:rgba(0,0,0,.1);border-radius:4px;display:inline-block;padding:10px}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-box.deprecated{opacity:.5}.swagger-ui .model-title{color:#505050;font-family:sans-serif;font-size:16px}.swagger-ui .model-title img{bottom:0;margin-left:1em;position:relative}.swagger-ui .model-deprecated-warning{color:#f93e3e;font-family:sans-serif;font-size:16px;font-weight:600;margin-right:1em}.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-name{display:inline-block;margin-right:1em}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#606060}.swagger-ui .servers>label{color:#3b4151;font-family:sans-serif;font-size:12px;margin:-20px 15px 0 0}.swagger-ui .servers>label select{max-width:100%;min-width:130px;width:100%}.swagger-ui .servers h4.message{padding-bottom:2em}.swagger-ui .servers table tr{width:30em}.swagger-ui .servers table td{display:inline-block;max-width:15em;padding-bottom:10px;padding-top:10px;vertical-align:middle}.swagger-ui .servers table td:first-of-type{padding-right:1em}.swagger-ui .servers table td input{height:100%;width:100%}.swagger-ui .servers .computed-url{margin:2em 0}.swagger-ui .servers .computed-url code{display:inline-block;font-size:16px;margin:0 1em;padding:4px}.swagger-ui .servers-title{font-size:12px;font-weight:700}.swagger-ui .operation-servers h4.message{margin-bottom:2em}.swagger-ui table{border-collapse:collapse;padding:0 10px;width:100%}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{padding:0 0 0 2em;width:174px}.swagger-ui table.headers td{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300;font-weight:600;vertical-align:middle}.swagger-ui table.headers .header-example{color:#999;font-style:italic}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{min-width:6em;padding:10px 0}.swagger-ui table tbody tr td:has(.model-box){max-width:1px}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid rgba(59,65,81,.2);color:#3b4151;font-family:sans-serif;font-size:12px;font-weight:700;padding:12px 0;text-align:left}.swagger-ui .parameters-col_description{margin-bottom:2em;width:99%}.swagger-ui .parameters-col_description input{max-width:340px;width:100%}.swagger-ui .parameters-col_description select{border-width:1px}.swagger-ui .parameters-col_description .markdown p,.swagger-ui .parameters-col_description .renderedMarkdown p{margin:0}.swagger-ui .parameter__name{color:#3b4151;font-family:sans-serif;font-size:16px;font-weight:400;margin-right:.75em}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required span{color:red}.swagger-ui .parameter__name.required:after{color:rgba(255,0,0,.6);content:"required";font-size:10px;padding:5px;position:relative;top:-6px}.swagger-ui .parameter__extension,.swagger-ui .parameter__in{color:grey;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .parameter__deprecated{color:red;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .parameter__empty_value_toggle{display:block;font-size:13px;padding-bottom:12px;padding-top:5px}.swagger-ui .parameter__empty_value_toggle input{margin-right:7px;width:auto}.swagger-ui .parameter__empty_value_toggle.disabled{opacity:.7}.swagger-ui .table-container{padding:20px}.swagger-ui .response-col_description{width:99%}.swagger-ui .response-col_description .markdown p,.swagger-ui .response-col_description .renderedMarkdown p{margin:0}.swagger-ui .response-col_links{min-width:6em}.swagger-ui .response__extension{color:grey;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .topbar{background-color:#1b1b1b;padding:10px 0}.swagger-ui .topbar .topbar-wrapper{align-items:center;display:flex;flex-wrap:wrap;gap:10px}@media(max-width:550px){.swagger-ui .topbar .topbar-wrapper{align-items:start;flex-direction:column}}.swagger-ui .topbar a{align-items:center;color:#fff;display:flex;flex:1;font-family:sans-serif;font-size:1.5em;font-weight:700;max-width:300px;-webkit-text-decoration:none;text-decoration:none}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:flex;flex:3;justify-content:flex-end}.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid #62a03f;border-radius:4px 0 0 4px;margin:0;max-width:100%;outline:none;width:100%}.swagger-ui .topbar .download-url-wrapper .select-label{align-items:center;color:#f0f0f0;display:flex;margin:0;max-width:600px;width:100%}.swagger-ui .topbar .download-url-wrapper .select-label span{flex:1;font-size:16px;padding:0 10px 0 0;text-align:right}.swagger-ui .topbar .download-url-wrapper .select-label select{border:2px solid #62a03f;box-shadow:none;flex:2;outline:none;width:100%}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:#62a03f;border:none;border-radius:0 4px 4px 0;color:#fff;font-family:sans-serif;font-size:16px;font-weight:700;padding:4px 30px}@media(max-width:550px){.swagger-ui .topbar .download-url-wrapper{width:100%}}.swagger-ui .info{margin:50px 0}.swagger-ui .info.failed-config{margin-left:auto;margin-right:auto;max-width:880px;text-align:center}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info pre,.swagger-ui .info table{font-size:14px}.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:#3b4151;font-family:sans-serif}.swagger-ui .info a{color:#4990e2;font-family:sans-serif;font-size:14px;transition:all .4s}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300!important;font-weight:600;margin:0}.swagger-ui .info .title{color:#3b4151;font-family:sans-serif;font-size:36px;margin:0}.swagger-ui .info .title small{background:#7d8492;border-radius:57px;display:inline-block;font-size:10px;margin:0 0 0 5px;padding:2px 4px;position:relative;top:-5px;vertical-align:super}.swagger-ui .info .title small.version-stamp{background-color:#89bf04}.swagger-ui .info .title small pre{color:#fff;font-family:sans-serif;margin:0;padding:0}.swagger-ui .auth-btn-wrapper{display:flex;justify-content:center;padding:10px 0}.swagger-ui .auth-btn-wrapper .btn-done{margin-right:1em}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{margin-left:10px;margin-right:10px;padding-right:20px}.swagger-ui .auth-container{border-bottom:1px solid #ebebeb;margin:0 0 10px;padding:10px 20px}.swagger-ui .auth-container:last-of-type{border:0;margin:0;padding:10px 20px}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{background-color:#fee;border-radius:4px;color:red;color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;margin:1em;padding:10px}.swagger-ui .auth-container .errors b{margin-right:1em;text-transform:capitalize}.swagger-ui .scopes h2{color:#3b4151;font-family:sans-serif;font-size:14px}.swagger-ui .scopes h2 a{color:#4990e2;cursor:pointer;font-size:12px;padding-left:10px;-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{animation:scaleUp .5s;background:rgba(249,62,62,.1);border:2px solid #f93e3e;border-radius:4px;margin:20px;padding:10px 20px}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{color:#3b4151;font-family:monospace;font-size:14px;font-weight:600;margin:0}.swagger-ui .errors-wrapper .errors small{color:#606060}.swagger-ui .errors-wrapper .errors .message{white-space:pre-line}.swagger-ui .errors-wrapper .errors .message.thrown{max-width:100%}.swagger-ui .errors-wrapper .errors .error-line{cursor:pointer;-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .errors-wrapper hgroup{align-items:center;display:flex}.swagger-ui .errors-wrapper hgroup h4{color:#3b4151;flex:1;font-family:sans-serif;font-size:20px;margin:0}@keyframes scaleUp{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}.swagger-ui .Resizer.vertical.disabled{display:none}.swagger-ui .markdown p,.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown p,.swagger-ui .renderedMarkdown pre{margin:1em auto;word-break:break-all;word-break:break-word}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{background:none;color:#000;font-weight:400;padding:0;white-space:pre-wrap}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:rgba(0,0,0,.05);border-radius:4px;color:#9012fe;font-family:monospace;font-size:14px;font-weight:600;padding:5px 7px}.swagger-ui .markdown pre>code,.swagger-ui .renderedMarkdown pre>code{display:block}.swagger-ui .json-schema-2020-12-keyword--\$vocabulary ul{border-left:1px dashed rgba(0,0,0,.1);margin:0 0 0 20px}.swagger-ui .json-schema-2020-12-\$vocabulary-uri{margin-left:35px}.swagger-ui .json-schema-2020-12-\$vocabulary-uri--disabled{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .json-schema-2020-12-keyword--const .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-keyword--const .json-schema-2020-12-json-viewer__value{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12__constraint{background-color:#805ad5;border-radius:4px;color:#3b4151;color:#fff;font-family:monospace;font-weight:600;line-height:1.5;margin-left:10px;padding:1px 3px}.swagger-ui .json-schema-2020-12__constraint--string{background-color:#d69e2e;color:#fff}.swagger-ui .json-schema-2020-12-keyword--default .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-keyword--default .json-schema-2020-12-json-viewer__value{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-keyword--dependentRequired>ul{display:inline-block;margin:0;padding:0}.swagger-ui .json-schema-2020-12-keyword--dependentRequired>ul li{display:inline;list-style-type:none}.swagger-ui .json-schema-2020-12-keyword--description{color:#6b6b6b;font-size:12px;margin-left:20px}.swagger-ui .json-schema-2020-12-keyword--description p{margin:0}.swagger-ui .json-schema-2020-12-keyword--enum .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-keyword--enum .json-schema-2020-12-json-viewer__value,.swagger-ui .json-schema-2020-12-keyword--examples .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-keyword--examples .json-schema-2020-12-json-viewer__value{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-json-viewer-extension-keyword .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-json-viewer-extension-keyword .json-schema-2020-12-json-viewer__value{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-keyword--patternProperties ul{border:none;margin:0;padding:0}.swagger-ui .json-schema-2020-12-keyword--patternProperties .json-schema-2020-12__title:first-of-type:after,.swagger-ui .json-schema-2020-12-keyword--patternProperties .json-schema-2020-12__title:first-of-type:before{color:#55a;content:"/"}.swagger-ui .json-schema-2020-12-keyword--properties>ul{border:none;margin:0;padding:0}.swagger-ui .json-schema-2020-12-property{list-style-type:none}.swagger-ui .json-schema-2020-12-property--required>.json-schema-2020-12:first-of-type>.json-schema-2020-12-head .json-schema-2020-12__title:after{color:red;content:"*";font-weight:700}.swagger-ui .json-schema-2020-12__title{color:#505050;display:inline-block;font-family:sans-serif;font-size:12px;font-weight:700;line-height:normal}.swagger-ui .json-schema-2020-12__title .json-schema-2020-12-keyword__name{margin:0}.swagger-ui .json-schema-2020-12-property{margin:7px 0}.swagger-ui .json-schema-2020-12-property .json-schema-2020-12__title{color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;vertical-align:middle}.swagger-ui .json-schema-2020-12-keyword{margin:5px 0}.swagger-ui .json-schema-2020-12-keyword__children{border-left:1px dashed rgba(0,0,0,.1);margin:0 0 0 20px;padding:0}.swagger-ui .json-schema-2020-12-keyword__children--collapsed{display:none}.swagger-ui .json-schema-2020-12-keyword__name{font-size:12px;font-weight:700;margin-left:20px}.swagger-ui .json-schema-2020-12-keyword__name--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-keyword__name--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__name--extension{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__value{color:#6b6b6b;font-size:12px;font-style:italic;font-weight:400}.swagger-ui .json-schema-2020-12-keyword__value--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-keyword__value--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__value--extension{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__value--warning{border:1px dashed red;border-radius:4px;color:#3b4151;color:red;display:inline-block;font-family:monospace;font-style:normal;font-weight:600;line-height:1.5;margin-left:10px;padding:1px 4px}.swagger-ui .json-schema-2020-12-keyword__name--secondary+.json-schema-2020-12-keyword__value--secondary:before{content:"="}.swagger-ui .json-schema-2020-12__attribute{color:#3b4151;font-family:monospace;font-size:12px;padding-left:10px;text-transform:lowercase}.swagger-ui .json-schema-2020-12__attribute--primary{color:#55a}.swagger-ui .json-schema-2020-12__attribute--muted{color:gray}.swagger-ui .json-schema-2020-12__attribute--warning{color:red}.swagger-ui .json-schema-2020-12-json-viewer{margin:5px 0}.swagger-ui .json-schema-2020-12-json-viewer__children{border-left:1px dashed rgba(0,0,0,.1);margin:0 0 0 20px;padding:0}.swagger-ui .json-schema-2020-12-json-viewer__children--collapsed{display:none}.swagger-ui .json-schema-2020-12-json-viewer__name{font-size:12px;font-weight:700;margin-left:20px}.swagger-ui .json-schema-2020-12-json-viewer__name--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-json-viewer__name--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-json-viewer__name--extension{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-json-viewer__value{color:#6b6b6b;font-size:12px;font-style:italic;font-weight:400}.swagger-ui .json-schema-2020-12-json-viewer__value--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-json-viewer__value--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-json-viewer__value--extension{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-json-viewer__value--warning{border:1px dashed red;border-radius:4px;color:#3b4151;color:red;display:inline-block;font-family:monospace;font-style:normal;font-weight:600;line-height:1.5;margin-left:10px;padding:1px 4px}.swagger-ui .json-schema-2020-12-json-viewer__name--secondary+.json-schema-2020-12-json-viewer__value--secondary:before{content:"="}.swagger-ui .json-schema-2020-12{background-color:rgba(0,0,0,.05);border-radius:4px;margin:0 20px 15px;padding:12px 0 12px 20px}.swagger-ui .json-schema-2020-12:first-of-type{margin:20px}.swagger-ui .json-schema-2020-12:last-of-type{margin:0 20px}.swagger-ui .json-schema-2020-12--embedded{background-color:inherit;padding-bottom:0;padding-left:inherit;padding-right:inherit;padding-top:0}.swagger-ui .json-schema-2020-12-body{border-left:1px dashed rgba(0,0,0,.1);margin:2px 0}.swagger-ui .json-schema-2020-12-body--collapsed{display:none}.swagger-ui .json-schema-2020-12-accordion{border:none;outline:none;padding-left:0}.swagger-ui .json-schema-2020-12-accordion__children{display:inline-block}.swagger-ui .json-schema-2020-12-accordion__icon{display:inline-block;height:18px;vertical-align:bottom;width:18px}.swagger-ui .json-schema-2020-12-accordion__icon--expanded{transform:rotate(-90deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .json-schema-2020-12-accordion__icon--collapsed{transform:rotate(0deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .json-schema-2020-12-accordion__icon svg{height:20px;width:20px}.swagger-ui .json-schema-2020-12-expand-deep-button{border:none;color:#505050;color:#afaeae;font-family:sans-serif;font-size:12px;padding-right:0}.swagger-ui .model-box .json-schema-2020-12:not(.json-schema-2020-12--embedded)>.json-schema-2020-12-head .json-schema-2020-12__title:first-of-type{font-size:16px}.swagger-ui .model-box>.json-schema-2020-12{margin:0}.swagger-ui .model-box .json-schema-2020-12{background-color:transparent;padding:0}.swagger-ui .model-box .json-schema-2020-12-accordion,.swagger-ui .model-box .json-schema-2020-12-expand-deep-button{background-color:transparent}.swagger-ui .models .json-schema-2020-12:not(.json-schema-2020-12--embedded)>.json-schema-2020-12-head .json-schema-2020-12__title:first-of-type{font-size:16px}.swagger-ui .models .json-schema-2020-12:not(.json-schema-2020-12--embedded){overflow-x:auto;width:calc(100% - 40px)} - -/*# sourceMappingURL=swagger-ui.css.map*/ \ No newline at end of file +.swagger-ui{color:#3b4151;font-family:sans-serif}.swagger-ui html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}.swagger-ui body{margin:0}.swagger-ui article,.swagger-ui aside,.swagger-ui footer,.swagger-ui header,.swagger-ui nav,.swagger-ui section{display:block}.swagger-ui h1{font-size:2em;margin:.67em 0}.swagger-ui figcaption,.swagger-ui figure,.swagger-ui main{display:block}.swagger-ui figure{margin:1em 40px}.swagger-ui hr{box-sizing:content-box;height:0;overflow:visible}.swagger-ui pre{font-family:monospace,monospace;font-size:1em}.swagger-ui a{background-color:transparent;-webkit-text-decoration-skip:objects}.swagger-ui abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.swagger-ui b,.swagger-ui strong{font-weight:inherit;font-weight:bolder}.swagger-ui code,.swagger-ui kbd,.swagger-ui samp{font-family:monospace,monospace;font-size:1em}.swagger-ui dfn{font-style:italic}.swagger-ui mark{background-color:#ff0;color:#000}.swagger-ui small{font-size:80%}.swagger-ui sub,.swagger-ui sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}.swagger-ui sub{bottom:-.25em}.swagger-ui sup{top:-.5em}.swagger-ui audio,.swagger-ui video{display:inline-block}.swagger-ui audio:not([controls]){display:none;height:0}.swagger-ui img{border-style:none}.swagger-ui svg:not(:root){overflow:hidden}.swagger-ui button,.swagger-ui input,.swagger-ui optgroup,.swagger-ui select,.swagger-ui textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}.swagger-ui button,.swagger-ui input{overflow:visible}.swagger-ui button,.swagger-ui select{text-transform:none}.swagger-ui [type=reset],.swagger-ui [type=submit],.swagger-ui button,.swagger-ui html [type=button]{-webkit-appearance:button}.swagger-ui [type=button]::-moz-focus-inner,.swagger-ui [type=reset]::-moz-focus-inner,.swagger-ui [type=submit]::-moz-focus-inner,.swagger-ui button::-moz-focus-inner{border-style:none;padding:0}.swagger-ui [type=button]:-moz-focusring,.swagger-ui [type=reset]:-moz-focusring,.swagger-ui [type=submit]:-moz-focusring,.swagger-ui button:-moz-focusring{outline:1px dotted ButtonText}.swagger-ui fieldset{padding:.35em .75em .625em}.swagger-ui legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}.swagger-ui progress{display:inline-block;vertical-align:baseline}.swagger-ui textarea{overflow:auto}.swagger-ui [type=checkbox],.swagger-ui [type=radio]{box-sizing:border-box;padding:0}.swagger-ui [type=number]::-webkit-inner-spin-button,.swagger-ui [type=number]::-webkit-outer-spin-button{height:auto}.swagger-ui [type=search]{-webkit-appearance:textfield;outline-offset:-2px}.swagger-ui [type=search]::-webkit-search-cancel-button,.swagger-ui [type=search]::-webkit-search-decoration{-webkit-appearance:none}.swagger-ui ::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}.swagger-ui details,.swagger-ui menu{display:block}.swagger-ui summary{display:list-item}.swagger-ui canvas{display:inline-block}.swagger-ui [hidden],.swagger-ui template{display:none}.swagger-ui .debug *{outline:1px solid gold}.swagger-ui .debug-white *{outline:1px solid #fff}.swagger-ui .debug-black *{outline:1px solid #000}.swagger-ui .debug-grid{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTRDOTY4N0U2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTRDOTY4N0Q2N0VFMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3NjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3NzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PsBS+GMAAAAjSURBVHjaYvz//z8DLsD4gcGXiYEAGBIKGBne//fFpwAgwAB98AaF2pjlUQAAAABJRU5ErkJggg==) repeat 0 0}.swagger-ui .debug-grid-16{background:transparent url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODYyRjhERDU2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODYyRjhERDQ2N0YyMTFFNjg2MzZDQjkwNkQ4MjgwMEIiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QTY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3QjY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PvCS01IAAABMSURBVHjaYmR4/5+BFPBfAMFm/MBgx8RAGWCn1AAmSg34Q6kBDKMGMDCwICeMIemF/5QawEipAWwUhwEjMDvbAWlWkvVBwu8vQIABAEwBCph8U6c0AAAAAElFTkSuQmCC) repeat 0 0}.swagger-ui .debug-grid-8-solid{background:#fff url(data:image/jpeg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAAAAAD/4QMxaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjYtYzExMSA3OS4xNTgzMjUsIDIwMTUvMDkvMTAtMDE6MTA6MjAgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE1IChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOkIxMjI0OTczNjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOkIxMjI0OTc0NjdCMzExRTZCMkJDRTI0MDgxMDAyMTcxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6QjEyMjQ5NzE2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6QjEyMjQ5NzI2N0IzMTFFNkIyQkNFMjQwODEwMDIxNzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAbGhopHSlBJiZBQi8vL0JHPz4+P0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHAR0pKTQmND8oKD9HPzU/R0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0dHR0f/wAARCAAIAAgDASIAAhEBAxEB/8QAWQABAQAAAAAAAAAAAAAAAAAAAAYBAQEAAAAAAAAAAAAAAAAAAAIEEAEBAAMBAAAAAAAAAAAAAAABADECA0ERAAEDBQAAAAAAAAAAAAAAAAARITFBUWESIv/aAAwDAQACEQMRAD8AoOnTV1QTD7JJshP3vSM3P//Z) repeat 0 0}.swagger-ui .debug-grid-16-solid{background:#fff url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTExIDc5LjE1ODMyNSwgMjAxNS8wOS8xMC0wMToxMDoyMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzY3MkJEN0U2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzY3MkJEN0Y2N0M1MTFFNkIyQkNFMjQwODEwMDIxNzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NjcyQkQ3QzY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NjcyQkQ3RDY3QzUxMUU2QjJCQ0UyNDA4MTAwMjE3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pve6J3kAAAAzSURBVHjaYvz//z8D0UDsMwMjSRoYP5Gq4SPNbRjVMEQ1fCRDg+in/6+J1AJUxsgAEGAA31BAJMS0GYEAAAAASUVORK5CYII=) repeat 0 0}.swagger-ui .border-box,.swagger-ui a,.swagger-ui article,.swagger-ui body,.swagger-ui code,.swagger-ui dd,.swagger-ui div,.swagger-ui dl,.swagger-ui dt,.swagger-ui fieldset,.swagger-ui footer,.swagger-ui form,.swagger-ui h1,.swagger-ui h2,.swagger-ui h3,.swagger-ui h4,.swagger-ui h5,.swagger-ui h6,.swagger-ui header,.swagger-ui html,.swagger-ui input[type=email],.swagger-ui input[type=number],.swagger-ui input[type=password],.swagger-ui input[type=tel],.swagger-ui input[type=text],.swagger-ui input[type=url],.swagger-ui legend,.swagger-ui li,.swagger-ui main,.swagger-ui ol,.swagger-ui p,.swagger-ui pre,.swagger-ui section,.swagger-ui table,.swagger-ui td,.swagger-ui textarea,.swagger-ui th,.swagger-ui tr,.swagger-ui ul{box-sizing:border-box}.swagger-ui .aspect-ratio{height:0;position:relative}.swagger-ui .aspect-ratio--16x9{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1{padding-bottom:100%}.swagger-ui .aspect-ratio--object{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}@media screen and (min-width:30em){.swagger-ui .aspect-ratio-ns{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-ns{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-ns{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-ns{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-ns{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-ns{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-ns{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-ns{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-ns{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-ns{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-ns{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-ns{padding-bottom:100%}.swagger-ui .aspect-ratio--object-ns{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .aspect-ratio-m{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-m{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-m{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-m{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-m{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-m{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-m{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-m{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-m{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-m{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-m{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-m{padding-bottom:100%}.swagger-ui .aspect-ratio--object-m{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}@media screen and (min-width:60em){.swagger-ui .aspect-ratio-l{height:0;position:relative}.swagger-ui .aspect-ratio--16x9-l{padding-bottom:56.25%}.swagger-ui .aspect-ratio--9x16-l{padding-bottom:177.77%}.swagger-ui .aspect-ratio--4x3-l{padding-bottom:75%}.swagger-ui .aspect-ratio--3x4-l{padding-bottom:133.33%}.swagger-ui .aspect-ratio--6x4-l{padding-bottom:66.6%}.swagger-ui .aspect-ratio--4x6-l{padding-bottom:150%}.swagger-ui .aspect-ratio--8x5-l{padding-bottom:62.5%}.swagger-ui .aspect-ratio--5x8-l{padding-bottom:160%}.swagger-ui .aspect-ratio--7x5-l{padding-bottom:71.42%}.swagger-ui .aspect-ratio--5x7-l{padding-bottom:140%}.swagger-ui .aspect-ratio--1x1-l{padding-bottom:100%}.swagger-ui .aspect-ratio--object-l{bottom:0;height:100%;left:0;position:absolute;right:0;top:0;width:100%;z-index:100}}.swagger-ui img{max-width:100%}.swagger-ui .cover{background-size:cover!important}.swagger-ui .contain{background-size:contain!important}@media screen and (min-width:30em){.swagger-ui .cover-ns{background-size:cover!important}.swagger-ui .contain-ns{background-size:contain!important}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .cover-m{background-size:cover!important}.swagger-ui .contain-m{background-size:contain!important}}@media screen and (min-width:60em){.swagger-ui .cover-l{background-size:cover!important}.swagger-ui .contain-l{background-size:contain!important}}.swagger-ui .bg-center{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left{background-position:0;background-repeat:no-repeat}@media screen and (min-width:30em){.swagger-ui .bg-center-ns{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-ns{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-ns{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-ns{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-ns{background-position:0;background-repeat:no-repeat}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .bg-center-m{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-m{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-m{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-m{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-m{background-position:0;background-repeat:no-repeat}}@media screen and (min-width:60em){.swagger-ui .bg-center-l{background-position:50%;background-repeat:no-repeat}.swagger-ui .bg-top-l{background-position:top;background-repeat:no-repeat}.swagger-ui .bg-right-l{background-position:100%;background-repeat:no-repeat}.swagger-ui .bg-bottom-l{background-position:bottom;background-repeat:no-repeat}.swagger-ui .bg-left-l{background-position:0;background-repeat:no-repeat}}.swagger-ui .outline{outline:1px solid}.swagger-ui .outline-transparent{outline:1px solid transparent}.swagger-ui .outline-0{outline:0}@media screen and (min-width:30em){.swagger-ui .outline-ns{outline:1px solid}.swagger-ui .outline-transparent-ns{outline:1px solid transparent}.swagger-ui .outline-0-ns{outline:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .outline-m{outline:1px solid}.swagger-ui .outline-transparent-m{outline:1px solid transparent}.swagger-ui .outline-0-m{outline:0}}@media screen and (min-width:60em){.swagger-ui .outline-l{outline:1px solid}.swagger-ui .outline-transparent-l{outline:1px solid transparent}.swagger-ui .outline-0-l{outline:0}}.swagger-ui .ba{border-style:solid;border-width:1px}.swagger-ui .bt{border-top-style:solid;border-top-width:1px}.swagger-ui .br{border-right-style:solid;border-right-width:1px}.swagger-ui .bb{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl{border-left-style:solid;border-left-width:1px}.swagger-ui .bn{border-style:none;border-width:0}@media screen and (min-width:30em){.swagger-ui .ba-ns{border-style:solid;border-width:1px}.swagger-ui .bt-ns{border-top-style:solid;border-top-width:1px}.swagger-ui .br-ns{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-ns{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-ns{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-ns{border-style:none;border-width:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ba-m{border-style:solid;border-width:1px}.swagger-ui .bt-m{border-top-style:solid;border-top-width:1px}.swagger-ui .br-m{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-m{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-m{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-m{border-style:none;border-width:0}}@media screen and (min-width:60em){.swagger-ui .ba-l{border-style:solid;border-width:1px}.swagger-ui .bt-l{border-top-style:solid;border-top-width:1px}.swagger-ui .br-l{border-right-style:solid;border-right-width:1px}.swagger-ui .bb-l{border-bottom-style:solid;border-bottom-width:1px}.swagger-ui .bl-l{border-left-style:solid;border-left-width:1px}.swagger-ui .bn-l{border-style:none;border-width:0}}.swagger-ui .b--black{border-color:#000}.swagger-ui .b--near-black{border-color:#111}.swagger-ui .b--dark-gray{border-color:#333}.swagger-ui .b--mid-gray{border-color:#555}.swagger-ui .b--gray{border-color:#777}.swagger-ui .b--silver{border-color:#999}.swagger-ui .b--light-silver{border-color:#aaa}.swagger-ui .b--moon-gray{border-color:#ccc}.swagger-ui .b--light-gray{border-color:#eee}.swagger-ui .b--near-white{border-color:#f4f4f4}.swagger-ui .b--white{border-color:#fff}.swagger-ui .b--white-90{border-color:hsla(0,0%,100%,.9)}.swagger-ui .b--white-80{border-color:hsla(0,0%,100%,.8)}.swagger-ui .b--white-70{border-color:hsla(0,0%,100%,.7)}.swagger-ui .b--white-60{border-color:hsla(0,0%,100%,.6)}.swagger-ui .b--white-50{border-color:hsla(0,0%,100%,.5)}.swagger-ui .b--white-40{border-color:hsla(0,0%,100%,.4)}.swagger-ui .b--white-30{border-color:hsla(0,0%,100%,.3)}.swagger-ui .b--white-20{border-color:hsla(0,0%,100%,.2)}.swagger-ui .b--white-10{border-color:hsla(0,0%,100%,.1)}.swagger-ui .b--white-05{border-color:hsla(0,0%,100%,.05)}.swagger-ui .b--white-025{border-color:hsla(0,0%,100%,.025)}.swagger-ui .b--white-0125{border-color:hsla(0,0%,100%,.013)}.swagger-ui .b--black-90{border-color:rgba(0,0,0,.9)}.swagger-ui .b--black-80{border-color:rgba(0,0,0,.8)}.swagger-ui .b--black-70{border-color:rgba(0,0,0,.7)}.swagger-ui .b--black-60{border-color:rgba(0,0,0,.6)}.swagger-ui .b--black-50{border-color:rgba(0,0,0,.5)}.swagger-ui .b--black-40{border-color:rgba(0,0,0,.4)}.swagger-ui .b--black-30{border-color:rgba(0,0,0,.3)}.swagger-ui .b--black-20{border-color:rgba(0,0,0,.2)}.swagger-ui .b--black-10{border-color:rgba(0,0,0,.1)}.swagger-ui .b--black-05{border-color:rgba(0,0,0,.05)}.swagger-ui .b--black-025{border-color:rgba(0,0,0,.025)}.swagger-ui .b--black-0125{border-color:rgba(0,0,0,.013)}.swagger-ui .b--dark-red{border-color:#e7040f}.swagger-ui .b--red{border-color:#ff4136}.swagger-ui .b--light-red{border-color:#ff725c}.swagger-ui .b--orange{border-color:#ff6300}.swagger-ui .b--gold{border-color:#ffb700}.swagger-ui .b--yellow{border-color:gold}.swagger-ui .b--light-yellow{border-color:#fbf1a9}.swagger-ui .b--purple{border-color:#5e2ca5}.swagger-ui .b--light-purple{border-color:#a463f2}.swagger-ui .b--dark-pink{border-color:#d5008f}.swagger-ui .b--hot-pink{border-color:#ff41b4}.swagger-ui .b--pink{border-color:#ff80cc}.swagger-ui .b--light-pink{border-color:#ffa3d7}.swagger-ui .b--dark-green{border-color:#137752}.swagger-ui .b--green{border-color:#19a974}.swagger-ui .b--light-green{border-color:#9eebcf}.swagger-ui .b--navy{border-color:#001b44}.swagger-ui .b--dark-blue{border-color:#00449e}.swagger-ui .b--blue{border-color:#357edd}.swagger-ui .b--light-blue{border-color:#96ccff}.swagger-ui .b--lightest-blue{border-color:#cdecff}.swagger-ui .b--washed-blue{border-color:#f6fffe}.swagger-ui .b--washed-green{border-color:#e8fdf5}.swagger-ui .b--washed-yellow{border-color:#fffceb}.swagger-ui .b--washed-red{border-color:#ffdfdf}.swagger-ui .b--transparent{border-color:transparent}.swagger-ui .b--inherit{border-color:inherit}.swagger-ui .br0{border-radius:0}.swagger-ui .br1{border-radius:.125rem}.swagger-ui .br2{border-radius:.25rem}.swagger-ui .br3{border-radius:.5rem}.swagger-ui .br4{border-radius:1rem}.swagger-ui .br-100{border-radius:100%}.swagger-ui .br-pill{border-radius:9999px}.swagger-ui .br--bottom{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left{border-bottom-right-radius:0;border-top-right-radius:0}@media screen and (min-width:30em){.swagger-ui .br0-ns{border-radius:0}.swagger-ui .br1-ns{border-radius:.125rem}.swagger-ui .br2-ns{border-radius:.25rem}.swagger-ui .br3-ns{border-radius:.5rem}.swagger-ui .br4-ns{border-radius:1rem}.swagger-ui .br-100-ns{border-radius:100%}.swagger-ui .br-pill-ns{border-radius:9999px}.swagger-ui .br--bottom-ns{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-ns{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-ns{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-ns{border-bottom-right-radius:0;border-top-right-radius:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .br0-m{border-radius:0}.swagger-ui .br1-m{border-radius:.125rem}.swagger-ui .br2-m{border-radius:.25rem}.swagger-ui .br3-m{border-radius:.5rem}.swagger-ui .br4-m{border-radius:1rem}.swagger-ui .br-100-m{border-radius:100%}.swagger-ui .br-pill-m{border-radius:9999px}.swagger-ui .br--bottom-m{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-m{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-m{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-m{border-bottom-right-radius:0;border-top-right-radius:0}}@media screen and (min-width:60em){.swagger-ui .br0-l{border-radius:0}.swagger-ui .br1-l{border-radius:.125rem}.swagger-ui .br2-l{border-radius:.25rem}.swagger-ui .br3-l{border-radius:.5rem}.swagger-ui .br4-l{border-radius:1rem}.swagger-ui .br-100-l{border-radius:100%}.swagger-ui .br-pill-l{border-radius:9999px}.swagger-ui .br--bottom-l{border-top-left-radius:0;border-top-right-radius:0}.swagger-ui .br--top-l{border-bottom-left-radius:0;border-bottom-right-radius:0}.swagger-ui .br--right-l{border-bottom-left-radius:0;border-top-left-radius:0}.swagger-ui .br--left-l{border-bottom-right-radius:0;border-top-right-radius:0}}.swagger-ui .b--dotted{border-style:dotted}.swagger-ui .b--dashed{border-style:dashed}.swagger-ui .b--solid{border-style:solid}.swagger-ui .b--none{border-style:none}@media screen and (min-width:30em){.swagger-ui .b--dotted-ns{border-style:dotted}.swagger-ui .b--dashed-ns{border-style:dashed}.swagger-ui .b--solid-ns{border-style:solid}.swagger-ui .b--none-ns{border-style:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .b--dotted-m{border-style:dotted}.swagger-ui .b--dashed-m{border-style:dashed}.swagger-ui .b--solid-m{border-style:solid}.swagger-ui .b--none-m{border-style:none}}@media screen and (min-width:60em){.swagger-ui .b--dotted-l{border-style:dotted}.swagger-ui .b--dashed-l{border-style:dashed}.swagger-ui .b--solid-l{border-style:solid}.swagger-ui .b--none-l{border-style:none}}.swagger-ui .bw0{border-width:0}.swagger-ui .bw1{border-width:.125rem}.swagger-ui .bw2{border-width:.25rem}.swagger-ui .bw3{border-width:.5rem}.swagger-ui .bw4{border-width:1rem}.swagger-ui .bw5{border-width:2rem}.swagger-ui .bt-0{border-top-width:0}.swagger-ui .br-0{border-right-width:0}.swagger-ui .bb-0{border-bottom-width:0}.swagger-ui .bl-0{border-left-width:0}@media screen and (min-width:30em){.swagger-ui .bw0-ns{border-width:0}.swagger-ui .bw1-ns{border-width:.125rem}.swagger-ui .bw2-ns{border-width:.25rem}.swagger-ui .bw3-ns{border-width:.5rem}.swagger-ui .bw4-ns{border-width:1rem}.swagger-ui .bw5-ns{border-width:2rem}.swagger-ui .bt-0-ns{border-top-width:0}.swagger-ui .br-0-ns{border-right-width:0}.swagger-ui .bb-0-ns{border-bottom-width:0}.swagger-ui .bl-0-ns{border-left-width:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .bw0-m{border-width:0}.swagger-ui .bw1-m{border-width:.125rem}.swagger-ui .bw2-m{border-width:.25rem}.swagger-ui .bw3-m{border-width:.5rem}.swagger-ui .bw4-m{border-width:1rem}.swagger-ui .bw5-m{border-width:2rem}.swagger-ui .bt-0-m{border-top-width:0}.swagger-ui .br-0-m{border-right-width:0}.swagger-ui .bb-0-m{border-bottom-width:0}.swagger-ui .bl-0-m{border-left-width:0}}@media screen and (min-width:60em){.swagger-ui .bw0-l{border-width:0}.swagger-ui .bw1-l{border-width:.125rem}.swagger-ui .bw2-l{border-width:.25rem}.swagger-ui .bw3-l{border-width:.5rem}.swagger-ui .bw4-l{border-width:1rem}.swagger-ui .bw5-l{border-width:2rem}.swagger-ui .bt-0-l{border-top-width:0}.swagger-ui .br-0-l{border-right-width:0}.swagger-ui .bb-0-l{border-bottom-width:0}.swagger-ui .bl-0-l{border-left-width:0}}.swagger-ui .shadow-1{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}@media screen and (min-width:30em){.swagger-ui .shadow-1-ns{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-ns{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-ns{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-ns{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-ns{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .shadow-1-m{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-m{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-m{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-m{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-m{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}@media screen and (min-width:60em){.swagger-ui .shadow-1-l{box-shadow:0 0 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-2-l{box-shadow:0 0 8px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-3-l{box-shadow:2px 2px 4px 2px rgba(0,0,0,.2)}.swagger-ui .shadow-4-l{box-shadow:2px 2px 8px 0 rgba(0,0,0,.2)}.swagger-ui .shadow-5-l{box-shadow:4px 4px 8px 0 rgba(0,0,0,.2)}}.swagger-ui .pre{overflow-x:auto;overflow-y:hidden;overflow:scroll}.swagger-ui .top-0{top:0}.swagger-ui .right-0{right:0}.swagger-ui .bottom-0{bottom:0}.swagger-ui .left-0{left:0}.swagger-ui .top-1{top:1rem}.swagger-ui .right-1{right:1rem}.swagger-ui .bottom-1{bottom:1rem}.swagger-ui .left-1{left:1rem}.swagger-ui .top-2{top:2rem}.swagger-ui .right-2{right:2rem}.swagger-ui .bottom-2{bottom:2rem}.swagger-ui .left-2{left:2rem}.swagger-ui .top--1{top:-1rem}.swagger-ui .right--1{right:-1rem}.swagger-ui .bottom--1{bottom:-1rem}.swagger-ui .left--1{left:-1rem}.swagger-ui .top--2{top:-2rem}.swagger-ui .right--2{right:-2rem}.swagger-ui .bottom--2{bottom:-2rem}.swagger-ui .left--2{left:-2rem}.swagger-ui .absolute--fill{bottom:0;left:0;right:0;top:0}@media screen and (min-width:30em){.swagger-ui .top-0-ns{top:0}.swagger-ui .left-0-ns{left:0}.swagger-ui .right-0-ns{right:0}.swagger-ui .bottom-0-ns{bottom:0}.swagger-ui .top-1-ns{top:1rem}.swagger-ui .left-1-ns{left:1rem}.swagger-ui .right-1-ns{right:1rem}.swagger-ui .bottom-1-ns{bottom:1rem}.swagger-ui .top-2-ns{top:2rem}.swagger-ui .left-2-ns{left:2rem}.swagger-ui .right-2-ns{right:2rem}.swagger-ui .bottom-2-ns{bottom:2rem}.swagger-ui .top--1-ns{top:-1rem}.swagger-ui .right--1-ns{right:-1rem}.swagger-ui .bottom--1-ns{bottom:-1rem}.swagger-ui .left--1-ns{left:-1rem}.swagger-ui .top--2-ns{top:-2rem}.swagger-ui .right--2-ns{right:-2rem}.swagger-ui .bottom--2-ns{bottom:-2rem}.swagger-ui .left--2-ns{left:-2rem}.swagger-ui .absolute--fill-ns{bottom:0;left:0;right:0;top:0}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .top-0-m{top:0}.swagger-ui .left-0-m{left:0}.swagger-ui .right-0-m{right:0}.swagger-ui .bottom-0-m{bottom:0}.swagger-ui .top-1-m{top:1rem}.swagger-ui .left-1-m{left:1rem}.swagger-ui .right-1-m{right:1rem}.swagger-ui .bottom-1-m{bottom:1rem}.swagger-ui .top-2-m{top:2rem}.swagger-ui .left-2-m{left:2rem}.swagger-ui .right-2-m{right:2rem}.swagger-ui .bottom-2-m{bottom:2rem}.swagger-ui .top--1-m{top:-1rem}.swagger-ui .right--1-m{right:-1rem}.swagger-ui .bottom--1-m{bottom:-1rem}.swagger-ui .left--1-m{left:-1rem}.swagger-ui .top--2-m{top:-2rem}.swagger-ui .right--2-m{right:-2rem}.swagger-ui .bottom--2-m{bottom:-2rem}.swagger-ui .left--2-m{left:-2rem}.swagger-ui .absolute--fill-m{bottom:0;left:0;right:0;top:0}}@media screen and (min-width:60em){.swagger-ui .top-0-l{top:0}.swagger-ui .left-0-l{left:0}.swagger-ui .right-0-l{right:0}.swagger-ui .bottom-0-l{bottom:0}.swagger-ui .top-1-l{top:1rem}.swagger-ui .left-1-l{left:1rem}.swagger-ui .right-1-l{right:1rem}.swagger-ui .bottom-1-l{bottom:1rem}.swagger-ui .top-2-l{top:2rem}.swagger-ui .left-2-l{left:2rem}.swagger-ui .right-2-l{right:2rem}.swagger-ui .bottom-2-l{bottom:2rem}.swagger-ui .top--1-l{top:-1rem}.swagger-ui .right--1-l{right:-1rem}.swagger-ui .bottom--1-l{bottom:-1rem}.swagger-ui .left--1-l{left:-1rem}.swagger-ui .top--2-l{top:-2rem}.swagger-ui .right--2-l{right:-2rem}.swagger-ui .bottom--2-l{bottom:-2rem}.swagger-ui .left--2-l{left:-2rem}.swagger-ui .absolute--fill-l{bottom:0;left:0;right:0;top:0}}.swagger-ui .cf:after,.swagger-ui .cf:before{content:" ";display:table}.swagger-ui .cf:after{clear:both}.swagger-ui .cf{zoom:1}.swagger-ui .cl{clear:left}.swagger-ui .cr{clear:right}.swagger-ui .cb{clear:both}.swagger-ui .cn{clear:none}@media screen and (min-width:30em){.swagger-ui .cl-ns{clear:left}.swagger-ui .cr-ns{clear:right}.swagger-ui .cb-ns{clear:both}.swagger-ui .cn-ns{clear:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .cl-m{clear:left}.swagger-ui .cr-m{clear:right}.swagger-ui .cb-m{clear:both}.swagger-ui .cn-m{clear:none}}@media screen and (min-width:60em){.swagger-ui .cl-l{clear:left}.swagger-ui .cr-l{clear:right}.swagger-ui .cb-l{clear:both}.swagger-ui .cn-l{clear:none}}.swagger-ui .flex{display:flex}.swagger-ui .inline-flex{display:inline-flex}.swagger-ui .flex-auto{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none{flex:none}.swagger-ui .flex-column{flex-direction:column}.swagger-ui .flex-row{flex-direction:row}.swagger-ui .flex-wrap{flex-wrap:wrap}.swagger-ui .flex-nowrap{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse{flex-direction:column-reverse}.swagger-ui .flex-row-reverse{flex-direction:row-reverse}.swagger-ui .items-start{align-items:flex-start}.swagger-ui .items-end{align-items:flex-end}.swagger-ui .items-center{align-items:center}.swagger-ui .items-baseline{align-items:baseline}.swagger-ui .items-stretch{align-items:stretch}.swagger-ui .self-start{align-self:flex-start}.swagger-ui .self-end{align-self:flex-end}.swagger-ui .self-center{align-self:center}.swagger-ui .self-baseline{align-self:baseline}.swagger-ui .self-stretch{align-self:stretch}.swagger-ui .justify-start{justify-content:flex-start}.swagger-ui .justify-end{justify-content:flex-end}.swagger-ui .justify-center{justify-content:center}.swagger-ui .justify-between{justify-content:space-between}.swagger-ui .justify-around{justify-content:space-around}.swagger-ui .content-start{align-content:flex-start}.swagger-ui .content-end{align-content:flex-end}.swagger-ui .content-center{align-content:center}.swagger-ui .content-between{align-content:space-between}.swagger-ui .content-around{align-content:space-around}.swagger-ui .content-stretch{align-content:stretch}.swagger-ui .order-0{order:0}.swagger-ui .order-1{order:1}.swagger-ui .order-2{order:2}.swagger-ui .order-3{order:3}.swagger-ui .order-4{order:4}.swagger-ui .order-5{order:5}.swagger-ui .order-6{order:6}.swagger-ui .order-7{order:7}.swagger-ui .order-8{order:8}.swagger-ui .order-last{order:99999}.swagger-ui .flex-grow-0{flex-grow:0}.swagger-ui .flex-grow-1{flex-grow:1}.swagger-ui .flex-shrink-0{flex-shrink:0}.swagger-ui .flex-shrink-1{flex-shrink:1}@media screen and (min-width:30em){.swagger-ui .flex-ns{display:flex}.swagger-ui .inline-flex-ns{display:inline-flex}.swagger-ui .flex-auto-ns{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-ns{flex:none}.swagger-ui .flex-column-ns{flex-direction:column}.swagger-ui .flex-row-ns{flex-direction:row}.swagger-ui .flex-wrap-ns{flex-wrap:wrap}.swagger-ui .flex-nowrap-ns{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-ns{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-ns{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-ns{flex-direction:row-reverse}.swagger-ui .items-start-ns{align-items:flex-start}.swagger-ui .items-end-ns{align-items:flex-end}.swagger-ui .items-center-ns{align-items:center}.swagger-ui .items-baseline-ns{align-items:baseline}.swagger-ui .items-stretch-ns{align-items:stretch}.swagger-ui .self-start-ns{align-self:flex-start}.swagger-ui .self-end-ns{align-self:flex-end}.swagger-ui .self-center-ns{align-self:center}.swagger-ui .self-baseline-ns{align-self:baseline}.swagger-ui .self-stretch-ns{align-self:stretch}.swagger-ui .justify-start-ns{justify-content:flex-start}.swagger-ui .justify-end-ns{justify-content:flex-end}.swagger-ui .justify-center-ns{justify-content:center}.swagger-ui .justify-between-ns{justify-content:space-between}.swagger-ui .justify-around-ns{justify-content:space-around}.swagger-ui .content-start-ns{align-content:flex-start}.swagger-ui .content-end-ns{align-content:flex-end}.swagger-ui .content-center-ns{align-content:center}.swagger-ui .content-between-ns{align-content:space-between}.swagger-ui .content-around-ns{align-content:space-around}.swagger-ui .content-stretch-ns{align-content:stretch}.swagger-ui .order-0-ns{order:0}.swagger-ui .order-1-ns{order:1}.swagger-ui .order-2-ns{order:2}.swagger-ui .order-3-ns{order:3}.swagger-ui .order-4-ns{order:4}.swagger-ui .order-5-ns{order:5}.swagger-ui .order-6-ns{order:6}.swagger-ui .order-7-ns{order:7}.swagger-ui .order-8-ns{order:8}.swagger-ui .order-last-ns{order:99999}.swagger-ui .flex-grow-0-ns{flex-grow:0}.swagger-ui .flex-grow-1-ns{flex-grow:1}.swagger-ui .flex-shrink-0-ns{flex-shrink:0}.swagger-ui .flex-shrink-1-ns{flex-shrink:1}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .flex-m{display:flex}.swagger-ui .inline-flex-m{display:inline-flex}.swagger-ui .flex-auto-m{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-m{flex:none}.swagger-ui .flex-column-m{flex-direction:column}.swagger-ui .flex-row-m{flex-direction:row}.swagger-ui .flex-wrap-m{flex-wrap:wrap}.swagger-ui .flex-nowrap-m{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-m{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-m{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-m{flex-direction:row-reverse}.swagger-ui .items-start-m{align-items:flex-start}.swagger-ui .items-end-m{align-items:flex-end}.swagger-ui .items-center-m{align-items:center}.swagger-ui .items-baseline-m{align-items:baseline}.swagger-ui .items-stretch-m{align-items:stretch}.swagger-ui .self-start-m{align-self:flex-start}.swagger-ui .self-end-m{align-self:flex-end}.swagger-ui .self-center-m{align-self:center}.swagger-ui .self-baseline-m{align-self:baseline}.swagger-ui .self-stretch-m{align-self:stretch}.swagger-ui .justify-start-m{justify-content:flex-start}.swagger-ui .justify-end-m{justify-content:flex-end}.swagger-ui .justify-center-m{justify-content:center}.swagger-ui .justify-between-m{justify-content:space-between}.swagger-ui .justify-around-m{justify-content:space-around}.swagger-ui .content-start-m{align-content:flex-start}.swagger-ui .content-end-m{align-content:flex-end}.swagger-ui .content-center-m{align-content:center}.swagger-ui .content-between-m{align-content:space-between}.swagger-ui .content-around-m{align-content:space-around}.swagger-ui .content-stretch-m{align-content:stretch}.swagger-ui .order-0-m{order:0}.swagger-ui .order-1-m{order:1}.swagger-ui .order-2-m{order:2}.swagger-ui .order-3-m{order:3}.swagger-ui .order-4-m{order:4}.swagger-ui .order-5-m{order:5}.swagger-ui .order-6-m{order:6}.swagger-ui .order-7-m{order:7}.swagger-ui .order-8-m{order:8}.swagger-ui .order-last-m{order:99999}.swagger-ui .flex-grow-0-m{flex-grow:0}.swagger-ui .flex-grow-1-m{flex-grow:1}.swagger-ui .flex-shrink-0-m{flex-shrink:0}.swagger-ui .flex-shrink-1-m{flex-shrink:1}}@media screen and (min-width:60em){.swagger-ui .flex-l{display:flex}.swagger-ui .inline-flex-l{display:inline-flex}.swagger-ui .flex-auto-l{flex:1 1 auto;min-height:0;min-width:0}.swagger-ui .flex-none-l{flex:none}.swagger-ui .flex-column-l{flex-direction:column}.swagger-ui .flex-row-l{flex-direction:row}.swagger-ui .flex-wrap-l{flex-wrap:wrap}.swagger-ui .flex-nowrap-l{flex-wrap:nowrap}.swagger-ui .flex-wrap-reverse-l{flex-wrap:wrap-reverse}.swagger-ui .flex-column-reverse-l{flex-direction:column-reverse}.swagger-ui .flex-row-reverse-l{flex-direction:row-reverse}.swagger-ui .items-start-l{align-items:flex-start}.swagger-ui .items-end-l{align-items:flex-end}.swagger-ui .items-center-l{align-items:center}.swagger-ui .items-baseline-l{align-items:baseline}.swagger-ui .items-stretch-l{align-items:stretch}.swagger-ui .self-start-l{align-self:flex-start}.swagger-ui .self-end-l{align-self:flex-end}.swagger-ui .self-center-l{align-self:center}.swagger-ui .self-baseline-l{align-self:baseline}.swagger-ui .self-stretch-l{align-self:stretch}.swagger-ui .justify-start-l{justify-content:flex-start}.swagger-ui .justify-end-l{justify-content:flex-end}.swagger-ui .justify-center-l{justify-content:center}.swagger-ui .justify-between-l{justify-content:space-between}.swagger-ui .justify-around-l{justify-content:space-around}.swagger-ui .content-start-l{align-content:flex-start}.swagger-ui .content-end-l{align-content:flex-end}.swagger-ui .content-center-l{align-content:center}.swagger-ui .content-between-l{align-content:space-between}.swagger-ui .content-around-l{align-content:space-around}.swagger-ui .content-stretch-l{align-content:stretch}.swagger-ui .order-0-l{order:0}.swagger-ui .order-1-l{order:1}.swagger-ui .order-2-l{order:2}.swagger-ui .order-3-l{order:3}.swagger-ui .order-4-l{order:4}.swagger-ui .order-5-l{order:5}.swagger-ui .order-6-l{order:6}.swagger-ui .order-7-l{order:7}.swagger-ui .order-8-l{order:8}.swagger-ui .order-last-l{order:99999}.swagger-ui .flex-grow-0-l{flex-grow:0}.swagger-ui .flex-grow-1-l{flex-grow:1}.swagger-ui .flex-shrink-0-l{flex-shrink:0}.swagger-ui .flex-shrink-1-l{flex-shrink:1}}.swagger-ui .dn{display:none}.swagger-ui .di{display:inline}.swagger-ui .db{display:block}.swagger-ui .dib{display:inline-block}.swagger-ui .dit{display:inline-table}.swagger-ui .dt{display:table}.swagger-ui .dtc{display:table-cell}.swagger-ui .dt-row{display:table-row}.swagger-ui .dt-row-group{display:table-row-group}.swagger-ui .dt-column{display:table-column}.swagger-ui .dt-column-group{display:table-column-group}.swagger-ui .dt--fixed{table-layout:fixed;width:100%}@media screen and (min-width:30em){.swagger-ui .dn-ns{display:none}.swagger-ui .di-ns{display:inline}.swagger-ui .db-ns{display:block}.swagger-ui .dib-ns{display:inline-block}.swagger-ui .dit-ns{display:inline-table}.swagger-ui .dt-ns{display:table}.swagger-ui .dtc-ns{display:table-cell}.swagger-ui .dt-row-ns{display:table-row}.swagger-ui .dt-row-group-ns{display:table-row-group}.swagger-ui .dt-column-ns{display:table-column}.swagger-ui .dt-column-group-ns{display:table-column-group}.swagger-ui .dt--fixed-ns{table-layout:fixed;width:100%}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .dn-m{display:none}.swagger-ui .di-m{display:inline}.swagger-ui .db-m{display:block}.swagger-ui .dib-m{display:inline-block}.swagger-ui .dit-m{display:inline-table}.swagger-ui .dt-m{display:table}.swagger-ui .dtc-m{display:table-cell}.swagger-ui .dt-row-m{display:table-row}.swagger-ui .dt-row-group-m{display:table-row-group}.swagger-ui .dt-column-m{display:table-column}.swagger-ui .dt-column-group-m{display:table-column-group}.swagger-ui .dt--fixed-m{table-layout:fixed;width:100%}}@media screen and (min-width:60em){.swagger-ui .dn-l{display:none}.swagger-ui .di-l{display:inline}.swagger-ui .db-l{display:block}.swagger-ui .dib-l{display:inline-block}.swagger-ui .dit-l{display:inline-table}.swagger-ui .dt-l{display:table}.swagger-ui .dtc-l{display:table-cell}.swagger-ui .dt-row-l{display:table-row}.swagger-ui .dt-row-group-l{display:table-row-group}.swagger-ui .dt-column-l{display:table-column}.swagger-ui .dt-column-group-l{display:table-column-group}.swagger-ui .dt--fixed-l{table-layout:fixed;width:100%}}.swagger-ui .fl{_display:inline;float:left}.swagger-ui .fr{_display:inline;float:right}.swagger-ui .fn{float:none}@media screen and (min-width:30em){.swagger-ui .fl-ns{_display:inline;float:left}.swagger-ui .fr-ns{_display:inline;float:right}.swagger-ui .fn-ns{float:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .fl-m{_display:inline;float:left}.swagger-ui .fr-m{_display:inline;float:right}.swagger-ui .fn-m{float:none}}@media screen and (min-width:60em){.swagger-ui .fl-l{_display:inline;float:left}.swagger-ui .fr-l{_display:inline;float:right}.swagger-ui .fn-l{float:none}}.swagger-ui .sans-serif{font-family:-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica,helvetica neue,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.swagger-ui .serif{font-family:georgia,serif}.swagger-ui .system-sans-serif{font-family:sans-serif}.swagger-ui .system-serif{font-family:serif}.swagger-ui .code,.swagger-ui code{font-family:Consolas,monaco,monospace}.swagger-ui .courier{font-family:Courier Next,courier,monospace}.swagger-ui .helvetica{font-family:helvetica neue,helvetica,sans-serif}.swagger-ui .avenir{font-family:avenir next,avenir,sans-serif}.swagger-ui .athelas{font-family:athelas,georgia,serif}.swagger-ui .georgia{font-family:georgia,serif}.swagger-ui .times{font-family:times,serif}.swagger-ui .bodoni{font-family:Bodoni MT,serif}.swagger-ui .calisto{font-family:Calisto MT,serif}.swagger-ui .garamond{font-family:garamond,serif}.swagger-ui .baskerville{font-family:baskerville,serif}.swagger-ui .i{font-style:italic}.swagger-ui .fs-normal{font-style:normal}@media screen and (min-width:30em){.swagger-ui .i-ns{font-style:italic}.swagger-ui .fs-normal-ns{font-style:normal}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .i-m{font-style:italic}.swagger-ui .fs-normal-m{font-style:normal}}@media screen and (min-width:60em){.swagger-ui .i-l{font-style:italic}.swagger-ui .fs-normal-l{font-style:normal}}.swagger-ui .normal{font-weight:400}.swagger-ui .b{font-weight:700}.swagger-ui .fw1{font-weight:100}.swagger-ui .fw2{font-weight:200}.swagger-ui .fw3{font-weight:300}.swagger-ui .fw4{font-weight:400}.swagger-ui .fw5{font-weight:500}.swagger-ui .fw6{font-weight:600}.swagger-ui .fw7{font-weight:700}.swagger-ui .fw8{font-weight:800}.swagger-ui .fw9{font-weight:900}@media screen and (min-width:30em){.swagger-ui .normal-ns{font-weight:400}.swagger-ui .b-ns{font-weight:700}.swagger-ui .fw1-ns{font-weight:100}.swagger-ui .fw2-ns{font-weight:200}.swagger-ui .fw3-ns{font-weight:300}.swagger-ui .fw4-ns{font-weight:400}.swagger-ui .fw5-ns{font-weight:500}.swagger-ui .fw6-ns{font-weight:600}.swagger-ui .fw7-ns{font-weight:700}.swagger-ui .fw8-ns{font-weight:800}.swagger-ui .fw9-ns{font-weight:900}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .normal-m{font-weight:400}.swagger-ui .b-m{font-weight:700}.swagger-ui .fw1-m{font-weight:100}.swagger-ui .fw2-m{font-weight:200}.swagger-ui .fw3-m{font-weight:300}.swagger-ui .fw4-m{font-weight:400}.swagger-ui .fw5-m{font-weight:500}.swagger-ui .fw6-m{font-weight:600}.swagger-ui .fw7-m{font-weight:700}.swagger-ui .fw8-m{font-weight:800}.swagger-ui .fw9-m{font-weight:900}}@media screen and (min-width:60em){.swagger-ui .normal-l{font-weight:400}.swagger-ui .b-l{font-weight:700}.swagger-ui .fw1-l{font-weight:100}.swagger-ui .fw2-l{font-weight:200}.swagger-ui .fw3-l{font-weight:300}.swagger-ui .fw4-l{font-weight:400}.swagger-ui .fw5-l{font-weight:500}.swagger-ui .fw6-l{font-weight:600}.swagger-ui .fw7-l{font-weight:700}.swagger-ui .fw8-l{font-weight:800}.swagger-ui .fw9-l{font-weight:900}}.swagger-ui .input-reset{-webkit-appearance:none;-moz-appearance:none}.swagger-ui .button-reset::-moz-focus-inner,.swagger-ui .input-reset::-moz-focus-inner{border:0;padding:0}.swagger-ui .h1{height:1rem}.swagger-ui .h2{height:2rem}.swagger-ui .h3{height:4rem}.swagger-ui .h4{height:8rem}.swagger-ui .h5{height:16rem}.swagger-ui .h-25{height:25%}.swagger-ui .h-50{height:50%}.swagger-ui .h-75{height:75%}.swagger-ui .h-100{height:100%}.swagger-ui .min-h-100{min-height:100%}.swagger-ui .vh-25{height:25vh}.swagger-ui .vh-50{height:50vh}.swagger-ui .vh-75{height:75vh}.swagger-ui .vh-100{height:100vh}.swagger-ui .min-vh-100{min-height:100vh}.swagger-ui .h-auto{height:auto}.swagger-ui .h-inherit{height:inherit}@media screen and (min-width:30em){.swagger-ui .h1-ns{height:1rem}.swagger-ui .h2-ns{height:2rem}.swagger-ui .h3-ns{height:4rem}.swagger-ui .h4-ns{height:8rem}.swagger-ui .h5-ns{height:16rem}.swagger-ui .h-25-ns{height:25%}.swagger-ui .h-50-ns{height:50%}.swagger-ui .h-75-ns{height:75%}.swagger-ui .h-100-ns{height:100%}.swagger-ui .min-h-100-ns{min-height:100%}.swagger-ui .vh-25-ns{height:25vh}.swagger-ui .vh-50-ns{height:50vh}.swagger-ui .vh-75-ns{height:75vh}.swagger-ui .vh-100-ns{height:100vh}.swagger-ui .min-vh-100-ns{min-height:100vh}.swagger-ui .h-auto-ns{height:auto}.swagger-ui .h-inherit-ns{height:inherit}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .h1-m{height:1rem}.swagger-ui .h2-m{height:2rem}.swagger-ui .h3-m{height:4rem}.swagger-ui .h4-m{height:8rem}.swagger-ui .h5-m{height:16rem}.swagger-ui .h-25-m{height:25%}.swagger-ui .h-50-m{height:50%}.swagger-ui .h-75-m{height:75%}.swagger-ui .h-100-m{height:100%}.swagger-ui .min-h-100-m{min-height:100%}.swagger-ui .vh-25-m{height:25vh}.swagger-ui .vh-50-m{height:50vh}.swagger-ui .vh-75-m{height:75vh}.swagger-ui .vh-100-m{height:100vh}.swagger-ui .min-vh-100-m{min-height:100vh}.swagger-ui .h-auto-m{height:auto}.swagger-ui .h-inherit-m{height:inherit}}@media screen and (min-width:60em){.swagger-ui .h1-l{height:1rem}.swagger-ui .h2-l{height:2rem}.swagger-ui .h3-l{height:4rem}.swagger-ui .h4-l{height:8rem}.swagger-ui .h5-l{height:16rem}.swagger-ui .h-25-l{height:25%}.swagger-ui .h-50-l{height:50%}.swagger-ui .h-75-l{height:75%}.swagger-ui .h-100-l{height:100%}.swagger-ui .min-h-100-l{min-height:100%}.swagger-ui .vh-25-l{height:25vh}.swagger-ui .vh-50-l{height:50vh}.swagger-ui .vh-75-l{height:75vh}.swagger-ui .vh-100-l{height:100vh}.swagger-ui .min-vh-100-l{min-height:100vh}.swagger-ui .h-auto-l{height:auto}.swagger-ui .h-inherit-l{height:inherit}}.swagger-ui .tracked{letter-spacing:.1em}.swagger-ui .tracked-tight{letter-spacing:-.05em}.swagger-ui .tracked-mega{letter-spacing:.25em}@media screen and (min-width:30em){.swagger-ui .tracked-ns{letter-spacing:.1em}.swagger-ui .tracked-tight-ns{letter-spacing:-.05em}.swagger-ui .tracked-mega-ns{letter-spacing:.25em}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .tracked-m{letter-spacing:.1em}.swagger-ui .tracked-tight-m{letter-spacing:-.05em}.swagger-ui .tracked-mega-m{letter-spacing:.25em}}@media screen and (min-width:60em){.swagger-ui .tracked-l{letter-spacing:.1em}.swagger-ui .tracked-tight-l{letter-spacing:-.05em}.swagger-ui .tracked-mega-l{letter-spacing:.25em}}.swagger-ui .lh-solid{line-height:1}.swagger-ui .lh-title{line-height:1.25}.swagger-ui .lh-copy{line-height:1.5}@media screen and (min-width:30em){.swagger-ui .lh-solid-ns{line-height:1}.swagger-ui .lh-title-ns{line-height:1.25}.swagger-ui .lh-copy-ns{line-height:1.5}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .lh-solid-m{line-height:1}.swagger-ui .lh-title-m{line-height:1.25}.swagger-ui .lh-copy-m{line-height:1.5}}@media screen and (min-width:60em){.swagger-ui .lh-solid-l{line-height:1}.swagger-ui .lh-title-l{line-height:1.25}.swagger-ui .lh-copy-l{line-height:1.5}}.swagger-ui .link{-webkit-text-decoration:none;text-decoration:none}.swagger-ui .link,.swagger-ui .link:active,.swagger-ui .link:focus,.swagger-ui .link:hover,.swagger-ui .link:link,.swagger-ui .link:visited{transition:color .15s ease-in}.swagger-ui .link:focus{outline:1px dotted currentColor}.swagger-ui .list{list-style-type:none}.swagger-ui .mw-100{max-width:100%}.swagger-ui .mw1{max-width:1rem}.swagger-ui .mw2{max-width:2rem}.swagger-ui .mw3{max-width:4rem}.swagger-ui .mw4{max-width:8rem}.swagger-ui .mw5{max-width:16rem}.swagger-ui .mw6{max-width:32rem}.swagger-ui .mw7{max-width:48rem}.swagger-ui .mw8{max-width:64rem}.swagger-ui .mw9{max-width:96rem}.swagger-ui .mw-none{max-width:none}@media screen and (min-width:30em){.swagger-ui .mw-100-ns{max-width:100%}.swagger-ui .mw1-ns{max-width:1rem}.swagger-ui .mw2-ns{max-width:2rem}.swagger-ui .mw3-ns{max-width:4rem}.swagger-ui .mw4-ns{max-width:8rem}.swagger-ui .mw5-ns{max-width:16rem}.swagger-ui .mw6-ns{max-width:32rem}.swagger-ui .mw7-ns{max-width:48rem}.swagger-ui .mw8-ns{max-width:64rem}.swagger-ui .mw9-ns{max-width:96rem}.swagger-ui .mw-none-ns{max-width:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .mw-100-m{max-width:100%}.swagger-ui .mw1-m{max-width:1rem}.swagger-ui .mw2-m{max-width:2rem}.swagger-ui .mw3-m{max-width:4rem}.swagger-ui .mw4-m{max-width:8rem}.swagger-ui .mw5-m{max-width:16rem}.swagger-ui .mw6-m{max-width:32rem}.swagger-ui .mw7-m{max-width:48rem}.swagger-ui .mw8-m{max-width:64rem}.swagger-ui .mw9-m{max-width:96rem}.swagger-ui .mw-none-m{max-width:none}}@media screen and (min-width:60em){.swagger-ui .mw-100-l{max-width:100%}.swagger-ui .mw1-l{max-width:1rem}.swagger-ui .mw2-l{max-width:2rem}.swagger-ui .mw3-l{max-width:4rem}.swagger-ui .mw4-l{max-width:8rem}.swagger-ui .mw5-l{max-width:16rem}.swagger-ui .mw6-l{max-width:32rem}.swagger-ui .mw7-l{max-width:48rem}.swagger-ui .mw8-l{max-width:64rem}.swagger-ui .mw9-l{max-width:96rem}.swagger-ui .mw-none-l{max-width:none}}.swagger-ui .w1{width:1rem}.swagger-ui .w2{width:2rem}.swagger-ui .w3{width:4rem}.swagger-ui .w4{width:8rem}.swagger-ui .w5{width:16rem}.swagger-ui .w-10{width:10%}.swagger-ui .w-20{width:20%}.swagger-ui .w-25{width:25%}.swagger-ui .w-30{width:30%}.swagger-ui .w-33{width:33%}.swagger-ui .w-34{width:34%}.swagger-ui .w-40{width:40%}.swagger-ui .w-50{width:50%}.swagger-ui .w-60{width:60%}.swagger-ui .w-70{width:70%}.swagger-ui .w-75{width:75%}.swagger-ui .w-80{width:80%}.swagger-ui .w-90{width:90%}.swagger-ui .w-100{width:100%}.swagger-ui .w-third{width:33.3333333333%}.swagger-ui .w-two-thirds{width:66.6666666667%}.swagger-ui .w-auto{width:auto}@media screen and (min-width:30em){.swagger-ui .w1-ns{width:1rem}.swagger-ui .w2-ns{width:2rem}.swagger-ui .w3-ns{width:4rem}.swagger-ui .w4-ns{width:8rem}.swagger-ui .w5-ns{width:16rem}.swagger-ui .w-10-ns{width:10%}.swagger-ui .w-20-ns{width:20%}.swagger-ui .w-25-ns{width:25%}.swagger-ui .w-30-ns{width:30%}.swagger-ui .w-33-ns{width:33%}.swagger-ui .w-34-ns{width:34%}.swagger-ui .w-40-ns{width:40%}.swagger-ui .w-50-ns{width:50%}.swagger-ui .w-60-ns{width:60%}.swagger-ui .w-70-ns{width:70%}.swagger-ui .w-75-ns{width:75%}.swagger-ui .w-80-ns{width:80%}.swagger-ui .w-90-ns{width:90%}.swagger-ui .w-100-ns{width:100%}.swagger-ui .w-third-ns{width:33.3333333333%}.swagger-ui .w-two-thirds-ns{width:66.6666666667%}.swagger-ui .w-auto-ns{width:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .w1-m{width:1rem}.swagger-ui .w2-m{width:2rem}.swagger-ui .w3-m{width:4rem}.swagger-ui .w4-m{width:8rem}.swagger-ui .w5-m{width:16rem}.swagger-ui .w-10-m{width:10%}.swagger-ui .w-20-m{width:20%}.swagger-ui .w-25-m{width:25%}.swagger-ui .w-30-m{width:30%}.swagger-ui .w-33-m{width:33%}.swagger-ui .w-34-m{width:34%}.swagger-ui .w-40-m{width:40%}.swagger-ui .w-50-m{width:50%}.swagger-ui .w-60-m{width:60%}.swagger-ui .w-70-m{width:70%}.swagger-ui .w-75-m{width:75%}.swagger-ui .w-80-m{width:80%}.swagger-ui .w-90-m{width:90%}.swagger-ui .w-100-m{width:100%}.swagger-ui .w-third-m{width:33.3333333333%}.swagger-ui .w-two-thirds-m{width:66.6666666667%}.swagger-ui .w-auto-m{width:auto}}@media screen and (min-width:60em){.swagger-ui .w1-l{width:1rem}.swagger-ui .w2-l{width:2rem}.swagger-ui .w3-l{width:4rem}.swagger-ui .w4-l{width:8rem}.swagger-ui .w5-l{width:16rem}.swagger-ui .w-10-l{width:10%}.swagger-ui .w-20-l{width:20%}.swagger-ui .w-25-l{width:25%}.swagger-ui .w-30-l{width:30%}.swagger-ui .w-33-l{width:33%}.swagger-ui .w-34-l{width:34%}.swagger-ui .w-40-l{width:40%}.swagger-ui .w-50-l{width:50%}.swagger-ui .w-60-l{width:60%}.swagger-ui .w-70-l{width:70%}.swagger-ui .w-75-l{width:75%}.swagger-ui .w-80-l{width:80%}.swagger-ui .w-90-l{width:90%}.swagger-ui .w-100-l{width:100%}.swagger-ui .w-third-l{width:33.3333333333%}.swagger-ui .w-two-thirds-l{width:66.6666666667%}.swagger-ui .w-auto-l{width:auto}}.swagger-ui .overflow-visible{overflow:visible}.swagger-ui .overflow-hidden{overflow:hidden}.swagger-ui .overflow-scroll{overflow:scroll}.swagger-ui .overflow-auto{overflow:auto}.swagger-ui .overflow-x-visible{overflow-x:visible}.swagger-ui .overflow-x-hidden{overflow-x:hidden}.swagger-ui .overflow-x-scroll{overflow-x:scroll}.swagger-ui .overflow-x-auto{overflow-x:auto}.swagger-ui .overflow-y-visible{overflow-y:visible}.swagger-ui .overflow-y-hidden{overflow-y:hidden}.swagger-ui .overflow-y-scroll{overflow-y:scroll}.swagger-ui .overflow-y-auto{overflow-y:auto}@media screen and (min-width:30em){.swagger-ui .overflow-visible-ns{overflow:visible}.swagger-ui .overflow-hidden-ns{overflow:hidden}.swagger-ui .overflow-scroll-ns{overflow:scroll}.swagger-ui .overflow-auto-ns{overflow:auto}.swagger-ui .overflow-x-visible-ns{overflow-x:visible}.swagger-ui .overflow-x-hidden-ns{overflow-x:hidden}.swagger-ui .overflow-x-scroll-ns{overflow-x:scroll}.swagger-ui .overflow-x-auto-ns{overflow-x:auto}.swagger-ui .overflow-y-visible-ns{overflow-y:visible}.swagger-ui .overflow-y-hidden-ns{overflow-y:hidden}.swagger-ui .overflow-y-scroll-ns{overflow-y:scroll}.swagger-ui .overflow-y-auto-ns{overflow-y:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .overflow-visible-m{overflow:visible}.swagger-ui .overflow-hidden-m{overflow:hidden}.swagger-ui .overflow-scroll-m{overflow:scroll}.swagger-ui .overflow-auto-m{overflow:auto}.swagger-ui .overflow-x-visible-m{overflow-x:visible}.swagger-ui .overflow-x-hidden-m{overflow-x:hidden}.swagger-ui .overflow-x-scroll-m{overflow-x:scroll}.swagger-ui .overflow-x-auto-m{overflow-x:auto}.swagger-ui .overflow-y-visible-m{overflow-y:visible}.swagger-ui .overflow-y-hidden-m{overflow-y:hidden}.swagger-ui .overflow-y-scroll-m{overflow-y:scroll}.swagger-ui .overflow-y-auto-m{overflow-y:auto}}@media screen and (min-width:60em){.swagger-ui .overflow-visible-l{overflow:visible}.swagger-ui .overflow-hidden-l{overflow:hidden}.swagger-ui .overflow-scroll-l{overflow:scroll}.swagger-ui .overflow-auto-l{overflow:auto}.swagger-ui .overflow-x-visible-l{overflow-x:visible}.swagger-ui .overflow-x-hidden-l{overflow-x:hidden}.swagger-ui .overflow-x-scroll-l{overflow-x:scroll}.swagger-ui .overflow-x-auto-l{overflow-x:auto}.swagger-ui .overflow-y-visible-l{overflow-y:visible}.swagger-ui .overflow-y-hidden-l{overflow-y:hidden}.swagger-ui .overflow-y-scroll-l{overflow-y:scroll}.swagger-ui .overflow-y-auto-l{overflow-y:auto}}.swagger-ui .static{position:static}.swagger-ui .relative{position:relative}.swagger-ui .absolute{position:absolute}.swagger-ui .fixed{position:fixed}@media screen and (min-width:30em){.swagger-ui .static-ns{position:static}.swagger-ui .relative-ns{position:relative}.swagger-ui .absolute-ns{position:absolute}.swagger-ui .fixed-ns{position:fixed}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .static-m{position:static}.swagger-ui .relative-m{position:relative}.swagger-ui .absolute-m{position:absolute}.swagger-ui .fixed-m{position:fixed}}@media screen and (min-width:60em){.swagger-ui .static-l{position:static}.swagger-ui .relative-l{position:relative}.swagger-ui .absolute-l{position:absolute}.swagger-ui .fixed-l{position:fixed}}.swagger-ui .o-100{opacity:1}.swagger-ui .o-90{opacity:.9}.swagger-ui .o-80{opacity:.8}.swagger-ui .o-70{opacity:.7}.swagger-ui .o-60{opacity:.6}.swagger-ui .o-50{opacity:.5}.swagger-ui .o-40{opacity:.4}.swagger-ui .o-30{opacity:.3}.swagger-ui .o-20{opacity:.2}.swagger-ui .o-10{opacity:.1}.swagger-ui .o-05{opacity:.05}.swagger-ui .o-025{opacity:.025}.swagger-ui .o-0{opacity:0}.swagger-ui .rotate-45{transform:rotate(45deg)}.swagger-ui .rotate-90{transform:rotate(90deg)}.swagger-ui .rotate-135{transform:rotate(135deg)}.swagger-ui .rotate-180{transform:rotate(180deg)}.swagger-ui .rotate-225{transform:rotate(225deg)}.swagger-ui .rotate-270{transform:rotate(270deg)}.swagger-ui .rotate-315{transform:rotate(315deg)}@media screen and (min-width:30em){.swagger-ui .rotate-45-ns{transform:rotate(45deg)}.swagger-ui .rotate-90-ns{transform:rotate(90deg)}.swagger-ui .rotate-135-ns{transform:rotate(135deg)}.swagger-ui .rotate-180-ns{transform:rotate(180deg)}.swagger-ui .rotate-225-ns{transform:rotate(225deg)}.swagger-ui .rotate-270-ns{transform:rotate(270deg)}.swagger-ui .rotate-315-ns{transform:rotate(315deg)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .rotate-45-m{transform:rotate(45deg)}.swagger-ui .rotate-90-m{transform:rotate(90deg)}.swagger-ui .rotate-135-m{transform:rotate(135deg)}.swagger-ui .rotate-180-m{transform:rotate(180deg)}.swagger-ui .rotate-225-m{transform:rotate(225deg)}.swagger-ui .rotate-270-m{transform:rotate(270deg)}.swagger-ui .rotate-315-m{transform:rotate(315deg)}}@media screen and (min-width:60em){.swagger-ui .rotate-45-l{transform:rotate(45deg)}.swagger-ui .rotate-90-l{transform:rotate(90deg)}.swagger-ui .rotate-135-l{transform:rotate(135deg)}.swagger-ui .rotate-180-l{transform:rotate(180deg)}.swagger-ui .rotate-225-l{transform:rotate(225deg)}.swagger-ui .rotate-270-l{transform:rotate(270deg)}.swagger-ui .rotate-315-l{transform:rotate(315deg)}}.swagger-ui .black-90{color:rgba(0,0,0,.9)}.swagger-ui .black-80{color:rgba(0,0,0,.8)}.swagger-ui .black-70{color:rgba(0,0,0,.7)}.swagger-ui .black-60{color:rgba(0,0,0,.6)}.swagger-ui .black-50{color:rgba(0,0,0,.5)}.swagger-ui .black-40{color:rgba(0,0,0,.4)}.swagger-ui .black-30{color:rgba(0,0,0,.3)}.swagger-ui .black-20{color:rgba(0,0,0,.2)}.swagger-ui .black-10{color:rgba(0,0,0,.1)}.swagger-ui .black-05{color:rgba(0,0,0,.05)}.swagger-ui .white-90{color:hsla(0,0%,100%,.9)}.swagger-ui .white-80{color:hsla(0,0%,100%,.8)}.swagger-ui .white-70{color:hsla(0,0%,100%,.7)}.swagger-ui .white-60{color:hsla(0,0%,100%,.6)}.swagger-ui .white-50{color:hsla(0,0%,100%,.5)}.swagger-ui .white-40{color:hsla(0,0%,100%,.4)}.swagger-ui .white-30{color:hsla(0,0%,100%,.3)}.swagger-ui .white-20{color:hsla(0,0%,100%,.2)}.swagger-ui .white-10{color:hsla(0,0%,100%,.1)}.swagger-ui .black{color:#000}.swagger-ui .near-black{color:#111}.swagger-ui .dark-gray{color:#333}.swagger-ui .mid-gray{color:#555}.swagger-ui .gray{color:#777}.swagger-ui .silver{color:#999}.swagger-ui .light-silver{color:#aaa}.swagger-ui .moon-gray{color:#ccc}.swagger-ui .light-gray{color:#eee}.swagger-ui .near-white{color:#f4f4f4}.swagger-ui .white{color:#fff}.swagger-ui .dark-red{color:#e7040f}.swagger-ui .red{color:#ff4136}.swagger-ui .light-red{color:#ff725c}.swagger-ui .orange{color:#ff6300}.swagger-ui .gold{color:#ffb700}.swagger-ui .yellow{color:gold}.swagger-ui .light-yellow{color:#fbf1a9}.swagger-ui .purple{color:#5e2ca5}.swagger-ui .light-purple{color:#a463f2}.swagger-ui .dark-pink{color:#d5008f}.swagger-ui .hot-pink{color:#ff41b4}.swagger-ui .pink{color:#ff80cc}.swagger-ui .light-pink{color:#ffa3d7}.swagger-ui .dark-green{color:#137752}.swagger-ui .green{color:#19a974}.swagger-ui .light-green{color:#9eebcf}.swagger-ui .navy{color:#001b44}.swagger-ui .dark-blue{color:#00449e}.swagger-ui .blue{color:#357edd}.swagger-ui .light-blue{color:#96ccff}.swagger-ui .lightest-blue{color:#cdecff}.swagger-ui .washed-blue{color:#f6fffe}.swagger-ui .washed-green{color:#e8fdf5}.swagger-ui .washed-yellow{color:#fffceb}.swagger-ui .washed-red{color:#ffdfdf}.swagger-ui .color-inherit{color:inherit}.swagger-ui .bg-black-90{background-color:rgba(0,0,0,.9)}.swagger-ui .bg-black-80{background-color:rgba(0,0,0,.8)}.swagger-ui .bg-black-70{background-color:rgba(0,0,0,.7)}.swagger-ui .bg-black-60{background-color:rgba(0,0,0,.6)}.swagger-ui .bg-black-50{background-color:rgba(0,0,0,.5)}.swagger-ui .bg-black-40{background-color:rgba(0,0,0,.4)}.swagger-ui .bg-black-30{background-color:rgba(0,0,0,.3)}.swagger-ui .bg-black-20{background-color:rgba(0,0,0,.2)}.swagger-ui .bg-black-10{background-color:rgba(0,0,0,.1)}.swagger-ui .bg-black-05{background-color:rgba(0,0,0,.05)}.swagger-ui .bg-white-90{background-color:hsla(0,0%,100%,.9)}.swagger-ui .bg-white-80{background-color:hsla(0,0%,100%,.8)}.swagger-ui .bg-white-70{background-color:hsla(0,0%,100%,.7)}.swagger-ui .bg-white-60{background-color:hsla(0,0%,100%,.6)}.swagger-ui .bg-white-50{background-color:hsla(0,0%,100%,.5)}.swagger-ui .bg-white-40{background-color:hsla(0,0%,100%,.4)}.swagger-ui .bg-white-30{background-color:hsla(0,0%,100%,.3)}.swagger-ui .bg-white-20{background-color:hsla(0,0%,100%,.2)}.swagger-ui .bg-white-10{background-color:hsla(0,0%,100%,.1)}.swagger-ui .bg-black{background-color:#000}.swagger-ui .bg-near-black{background-color:#111}.swagger-ui .bg-dark-gray{background-color:#333}.swagger-ui .bg-mid-gray{background-color:#555}.swagger-ui .bg-gray{background-color:#777}.swagger-ui .bg-silver{background-color:#999}.swagger-ui .bg-light-silver{background-color:#aaa}.swagger-ui .bg-moon-gray{background-color:#ccc}.swagger-ui .bg-light-gray{background-color:#eee}.swagger-ui .bg-near-white{background-color:#f4f4f4}.swagger-ui .bg-white{background-color:#fff}.swagger-ui .bg-transparent{background-color:transparent}.swagger-ui .bg-dark-red{background-color:#e7040f}.swagger-ui .bg-red{background-color:#ff4136}.swagger-ui .bg-light-red{background-color:#ff725c}.swagger-ui .bg-orange{background-color:#ff6300}.swagger-ui .bg-gold{background-color:#ffb700}.swagger-ui .bg-yellow{background-color:gold}.swagger-ui .bg-light-yellow{background-color:#fbf1a9}.swagger-ui .bg-purple{background-color:#5e2ca5}.swagger-ui .bg-light-purple{background-color:#a463f2}.swagger-ui .bg-dark-pink{background-color:#d5008f}.swagger-ui .bg-hot-pink{background-color:#ff41b4}.swagger-ui .bg-pink{background-color:#ff80cc}.swagger-ui .bg-light-pink{background-color:#ffa3d7}.swagger-ui .bg-dark-green{background-color:#137752}.swagger-ui .bg-green{background-color:#19a974}.swagger-ui .bg-light-green{background-color:#9eebcf}.swagger-ui .bg-navy{background-color:#001b44}.swagger-ui .bg-dark-blue{background-color:#00449e}.swagger-ui .bg-blue{background-color:#357edd}.swagger-ui .bg-light-blue{background-color:#96ccff}.swagger-ui .bg-lightest-blue{background-color:#cdecff}.swagger-ui .bg-washed-blue{background-color:#f6fffe}.swagger-ui .bg-washed-green{background-color:#e8fdf5}.swagger-ui .bg-washed-yellow{background-color:#fffceb}.swagger-ui .bg-washed-red{background-color:#ffdfdf}.swagger-ui .bg-inherit{background-color:inherit}.swagger-ui .hover-black:focus,.swagger-ui .hover-black:hover{color:#000}.swagger-ui .hover-near-black:focus,.swagger-ui .hover-near-black:hover{color:#111}.swagger-ui .hover-dark-gray:focus,.swagger-ui .hover-dark-gray:hover{color:#333}.swagger-ui .hover-mid-gray:focus,.swagger-ui .hover-mid-gray:hover{color:#555}.swagger-ui .hover-gray:focus,.swagger-ui .hover-gray:hover{color:#777}.swagger-ui .hover-silver:focus,.swagger-ui .hover-silver:hover{color:#999}.swagger-ui .hover-light-silver:focus,.swagger-ui .hover-light-silver:hover{color:#aaa}.swagger-ui .hover-moon-gray:focus,.swagger-ui .hover-moon-gray:hover{color:#ccc}.swagger-ui .hover-light-gray:focus,.swagger-ui .hover-light-gray:hover{color:#eee}.swagger-ui .hover-near-white:focus,.swagger-ui .hover-near-white:hover{color:#f4f4f4}.swagger-ui .hover-white:focus,.swagger-ui .hover-white:hover{color:#fff}.swagger-ui .hover-black-90:focus,.swagger-ui .hover-black-90:hover{color:rgba(0,0,0,.9)}.swagger-ui .hover-black-80:focus,.swagger-ui .hover-black-80:hover{color:rgba(0,0,0,.8)}.swagger-ui .hover-black-70:focus,.swagger-ui .hover-black-70:hover{color:rgba(0,0,0,.7)}.swagger-ui .hover-black-60:focus,.swagger-ui .hover-black-60:hover{color:rgba(0,0,0,.6)}.swagger-ui .hover-black-50:focus,.swagger-ui .hover-black-50:hover{color:rgba(0,0,0,.5)}.swagger-ui .hover-black-40:focus,.swagger-ui .hover-black-40:hover{color:rgba(0,0,0,.4)}.swagger-ui .hover-black-30:focus,.swagger-ui .hover-black-30:hover{color:rgba(0,0,0,.3)}.swagger-ui .hover-black-20:focus,.swagger-ui .hover-black-20:hover{color:rgba(0,0,0,.2)}.swagger-ui .hover-black-10:focus,.swagger-ui .hover-black-10:hover{color:rgba(0,0,0,.1)}.swagger-ui .hover-white-90:focus,.swagger-ui .hover-white-90:hover{color:hsla(0,0%,100%,.9)}.swagger-ui .hover-white-80:focus,.swagger-ui .hover-white-80:hover{color:hsla(0,0%,100%,.8)}.swagger-ui .hover-white-70:focus,.swagger-ui .hover-white-70:hover{color:hsla(0,0%,100%,.7)}.swagger-ui .hover-white-60:focus,.swagger-ui .hover-white-60:hover{color:hsla(0,0%,100%,.6)}.swagger-ui .hover-white-50:focus,.swagger-ui .hover-white-50:hover{color:hsla(0,0%,100%,.5)}.swagger-ui .hover-white-40:focus,.swagger-ui .hover-white-40:hover{color:hsla(0,0%,100%,.4)}.swagger-ui .hover-white-30:focus,.swagger-ui .hover-white-30:hover{color:hsla(0,0%,100%,.3)}.swagger-ui .hover-white-20:focus,.swagger-ui .hover-white-20:hover{color:hsla(0,0%,100%,.2)}.swagger-ui .hover-white-10:focus,.swagger-ui .hover-white-10:hover{color:hsla(0,0%,100%,.1)}.swagger-ui .hover-inherit:focus,.swagger-ui .hover-inherit:hover{color:inherit}.swagger-ui .hover-bg-black:focus,.swagger-ui .hover-bg-black:hover{background-color:#000}.swagger-ui .hover-bg-near-black:focus,.swagger-ui .hover-bg-near-black:hover{background-color:#111}.swagger-ui .hover-bg-dark-gray:focus,.swagger-ui .hover-bg-dark-gray:hover{background-color:#333}.swagger-ui .hover-bg-mid-gray:focus,.swagger-ui .hover-bg-mid-gray:hover{background-color:#555}.swagger-ui .hover-bg-gray:focus,.swagger-ui .hover-bg-gray:hover{background-color:#777}.swagger-ui .hover-bg-silver:focus,.swagger-ui .hover-bg-silver:hover{background-color:#999}.swagger-ui .hover-bg-light-silver:focus,.swagger-ui .hover-bg-light-silver:hover{background-color:#aaa}.swagger-ui .hover-bg-moon-gray:focus,.swagger-ui .hover-bg-moon-gray:hover{background-color:#ccc}.swagger-ui .hover-bg-light-gray:focus,.swagger-ui .hover-bg-light-gray:hover{background-color:#eee}.swagger-ui .hover-bg-near-white:focus,.swagger-ui .hover-bg-near-white:hover{background-color:#f4f4f4}.swagger-ui .hover-bg-white:focus,.swagger-ui .hover-bg-white:hover{background-color:#fff}.swagger-ui .hover-bg-transparent:focus,.swagger-ui .hover-bg-transparent:hover{background-color:transparent}.swagger-ui .hover-bg-black-90:focus,.swagger-ui .hover-bg-black-90:hover{background-color:rgba(0,0,0,.9)}.swagger-ui .hover-bg-black-80:focus,.swagger-ui .hover-bg-black-80:hover{background-color:rgba(0,0,0,.8)}.swagger-ui .hover-bg-black-70:focus,.swagger-ui .hover-bg-black-70:hover{background-color:rgba(0,0,0,.7)}.swagger-ui .hover-bg-black-60:focus,.swagger-ui .hover-bg-black-60:hover{background-color:rgba(0,0,0,.6)}.swagger-ui .hover-bg-black-50:focus,.swagger-ui .hover-bg-black-50:hover{background-color:rgba(0,0,0,.5)}.swagger-ui .hover-bg-black-40:focus,.swagger-ui .hover-bg-black-40:hover{background-color:rgba(0,0,0,.4)}.swagger-ui .hover-bg-black-30:focus,.swagger-ui .hover-bg-black-30:hover{background-color:rgba(0,0,0,.3)}.swagger-ui .hover-bg-black-20:focus,.swagger-ui .hover-bg-black-20:hover{background-color:rgba(0,0,0,.2)}.swagger-ui .hover-bg-black-10:focus,.swagger-ui .hover-bg-black-10:hover{background-color:rgba(0,0,0,.1)}.swagger-ui .hover-bg-white-90:focus,.swagger-ui .hover-bg-white-90:hover{background-color:hsla(0,0%,100%,.9)}.swagger-ui .hover-bg-white-80:focus,.swagger-ui .hover-bg-white-80:hover{background-color:hsla(0,0%,100%,.8)}.swagger-ui .hover-bg-white-70:focus,.swagger-ui .hover-bg-white-70:hover{background-color:hsla(0,0%,100%,.7)}.swagger-ui .hover-bg-white-60:focus,.swagger-ui .hover-bg-white-60:hover{background-color:hsla(0,0%,100%,.6)}.swagger-ui .hover-bg-white-50:focus,.swagger-ui .hover-bg-white-50:hover{background-color:hsla(0,0%,100%,.5)}.swagger-ui .hover-bg-white-40:focus,.swagger-ui .hover-bg-white-40:hover{background-color:hsla(0,0%,100%,.4)}.swagger-ui .hover-bg-white-30:focus,.swagger-ui .hover-bg-white-30:hover{background-color:hsla(0,0%,100%,.3)}.swagger-ui .hover-bg-white-20:focus,.swagger-ui .hover-bg-white-20:hover{background-color:hsla(0,0%,100%,.2)}.swagger-ui .hover-bg-white-10:focus,.swagger-ui .hover-bg-white-10:hover{background-color:hsla(0,0%,100%,.1)}.swagger-ui .hover-dark-red:focus,.swagger-ui .hover-dark-red:hover{color:#e7040f}.swagger-ui .hover-red:focus,.swagger-ui .hover-red:hover{color:#ff4136}.swagger-ui .hover-light-red:focus,.swagger-ui .hover-light-red:hover{color:#ff725c}.swagger-ui .hover-orange:focus,.swagger-ui .hover-orange:hover{color:#ff6300}.swagger-ui .hover-gold:focus,.swagger-ui .hover-gold:hover{color:#ffb700}.swagger-ui .hover-yellow:focus,.swagger-ui .hover-yellow:hover{color:gold}.swagger-ui .hover-light-yellow:focus,.swagger-ui .hover-light-yellow:hover{color:#fbf1a9}.swagger-ui .hover-purple:focus,.swagger-ui .hover-purple:hover{color:#5e2ca5}.swagger-ui .hover-light-purple:focus,.swagger-ui .hover-light-purple:hover{color:#a463f2}.swagger-ui .hover-dark-pink:focus,.swagger-ui .hover-dark-pink:hover{color:#d5008f}.swagger-ui .hover-hot-pink:focus,.swagger-ui .hover-hot-pink:hover{color:#ff41b4}.swagger-ui .hover-pink:focus,.swagger-ui .hover-pink:hover{color:#ff80cc}.swagger-ui .hover-light-pink:focus,.swagger-ui .hover-light-pink:hover{color:#ffa3d7}.swagger-ui .hover-dark-green:focus,.swagger-ui .hover-dark-green:hover{color:#137752}.swagger-ui .hover-green:focus,.swagger-ui .hover-green:hover{color:#19a974}.swagger-ui .hover-light-green:focus,.swagger-ui .hover-light-green:hover{color:#9eebcf}.swagger-ui .hover-navy:focus,.swagger-ui .hover-navy:hover{color:#001b44}.swagger-ui .hover-dark-blue:focus,.swagger-ui .hover-dark-blue:hover{color:#00449e}.swagger-ui .hover-blue:focus,.swagger-ui .hover-blue:hover{color:#357edd}.swagger-ui .hover-light-blue:focus,.swagger-ui .hover-light-blue:hover{color:#96ccff}.swagger-ui .hover-lightest-blue:focus,.swagger-ui .hover-lightest-blue:hover{color:#cdecff}.swagger-ui .hover-washed-blue:focus,.swagger-ui .hover-washed-blue:hover{color:#f6fffe}.swagger-ui .hover-washed-green:focus,.swagger-ui .hover-washed-green:hover{color:#e8fdf5}.swagger-ui .hover-washed-yellow:focus,.swagger-ui .hover-washed-yellow:hover{color:#fffceb}.swagger-ui .hover-washed-red:focus,.swagger-ui .hover-washed-red:hover{color:#ffdfdf}.swagger-ui .hover-bg-dark-red:focus,.swagger-ui .hover-bg-dark-red:hover{background-color:#e7040f}.swagger-ui .hover-bg-red:focus,.swagger-ui .hover-bg-red:hover{background-color:#ff4136}.swagger-ui .hover-bg-light-red:focus,.swagger-ui .hover-bg-light-red:hover{background-color:#ff725c}.swagger-ui .hover-bg-orange:focus,.swagger-ui .hover-bg-orange:hover{background-color:#ff6300}.swagger-ui .hover-bg-gold:focus,.swagger-ui .hover-bg-gold:hover{background-color:#ffb700}.swagger-ui .hover-bg-yellow:focus,.swagger-ui .hover-bg-yellow:hover{background-color:gold}.swagger-ui .hover-bg-light-yellow:focus,.swagger-ui .hover-bg-light-yellow:hover{background-color:#fbf1a9}.swagger-ui .hover-bg-purple:focus,.swagger-ui .hover-bg-purple:hover{background-color:#5e2ca5}.swagger-ui .hover-bg-light-purple:focus,.swagger-ui .hover-bg-light-purple:hover{background-color:#a463f2}.swagger-ui .hover-bg-dark-pink:focus,.swagger-ui .hover-bg-dark-pink:hover{background-color:#d5008f}.swagger-ui .hover-bg-hot-pink:focus,.swagger-ui .hover-bg-hot-pink:hover{background-color:#ff41b4}.swagger-ui .hover-bg-pink:focus,.swagger-ui .hover-bg-pink:hover{background-color:#ff80cc}.swagger-ui .hover-bg-light-pink:focus,.swagger-ui .hover-bg-light-pink:hover{background-color:#ffa3d7}.swagger-ui .hover-bg-dark-green:focus,.swagger-ui .hover-bg-dark-green:hover{background-color:#137752}.swagger-ui .hover-bg-green:focus,.swagger-ui .hover-bg-green:hover{background-color:#19a974}.swagger-ui .hover-bg-light-green:focus,.swagger-ui .hover-bg-light-green:hover{background-color:#9eebcf}.swagger-ui .hover-bg-navy:focus,.swagger-ui .hover-bg-navy:hover{background-color:#001b44}.swagger-ui .hover-bg-dark-blue:focus,.swagger-ui .hover-bg-dark-blue:hover{background-color:#00449e}.swagger-ui .hover-bg-blue:focus,.swagger-ui .hover-bg-blue:hover{background-color:#357edd}.swagger-ui .hover-bg-light-blue:focus,.swagger-ui .hover-bg-light-blue:hover{background-color:#96ccff}.swagger-ui .hover-bg-lightest-blue:focus,.swagger-ui .hover-bg-lightest-blue:hover{background-color:#cdecff}.swagger-ui .hover-bg-washed-blue:focus,.swagger-ui .hover-bg-washed-blue:hover{background-color:#f6fffe}.swagger-ui .hover-bg-washed-green:focus,.swagger-ui .hover-bg-washed-green:hover{background-color:#e8fdf5}.swagger-ui .hover-bg-washed-yellow:focus,.swagger-ui .hover-bg-washed-yellow:hover{background-color:#fffceb}.swagger-ui .hover-bg-washed-red:focus,.swagger-ui .hover-bg-washed-red:hover{background-color:#ffdfdf}.swagger-ui .hover-bg-inherit:focus,.swagger-ui .hover-bg-inherit:hover{background-color:inherit}.swagger-ui .pa0{padding:0}.swagger-ui .pa1{padding:.25rem}.swagger-ui .pa2{padding:.5rem}.swagger-ui .pa3{padding:1rem}.swagger-ui .pa4{padding:2rem}.swagger-ui .pa5{padding:4rem}.swagger-ui .pa6{padding:8rem}.swagger-ui .pa7{padding:16rem}.swagger-ui .pl0{padding-left:0}.swagger-ui .pl1{padding-left:.25rem}.swagger-ui .pl2{padding-left:.5rem}.swagger-ui .pl3{padding-left:1rem}.swagger-ui .pl4{padding-left:2rem}.swagger-ui .pl5{padding-left:4rem}.swagger-ui .pl6{padding-left:8rem}.swagger-ui .pl7{padding-left:16rem}.swagger-ui .pr0{padding-right:0}.swagger-ui .pr1{padding-right:.25rem}.swagger-ui .pr2{padding-right:.5rem}.swagger-ui .pr3{padding-right:1rem}.swagger-ui .pr4{padding-right:2rem}.swagger-ui .pr5{padding-right:4rem}.swagger-ui .pr6{padding-right:8rem}.swagger-ui .pr7{padding-right:16rem}.swagger-ui .pb0{padding-bottom:0}.swagger-ui .pb1{padding-bottom:.25rem}.swagger-ui .pb2{padding-bottom:.5rem}.swagger-ui .pb3{padding-bottom:1rem}.swagger-ui .pb4{padding-bottom:2rem}.swagger-ui .pb5{padding-bottom:4rem}.swagger-ui .pb6{padding-bottom:8rem}.swagger-ui .pb7{padding-bottom:16rem}.swagger-ui .pt0{padding-top:0}.swagger-ui .pt1{padding-top:.25rem}.swagger-ui .pt2{padding-top:.5rem}.swagger-ui .pt3{padding-top:1rem}.swagger-ui .pt4{padding-top:2rem}.swagger-ui .pt5{padding-top:4rem}.swagger-ui .pt6{padding-top:8rem}.swagger-ui .pt7{padding-top:16rem}.swagger-ui .pv0{padding-bottom:0;padding-top:0}.swagger-ui .pv1{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0{padding-left:0;padding-right:0}.swagger-ui .ph1{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0{margin:0}.swagger-ui .ma1{margin:.25rem}.swagger-ui .ma2{margin:.5rem}.swagger-ui .ma3{margin:1rem}.swagger-ui .ma4{margin:2rem}.swagger-ui .ma5{margin:4rem}.swagger-ui .ma6{margin:8rem}.swagger-ui .ma7{margin:16rem}.swagger-ui .ml0{margin-left:0}.swagger-ui .ml1{margin-left:.25rem}.swagger-ui .ml2{margin-left:.5rem}.swagger-ui .ml3{margin-left:1rem}.swagger-ui .ml4{margin-left:2rem}.swagger-ui .ml5{margin-left:4rem}.swagger-ui .ml6{margin-left:8rem}.swagger-ui .ml7{margin-left:16rem}.swagger-ui .mr0{margin-right:0}.swagger-ui .mr1{margin-right:.25rem}.swagger-ui .mr2{margin-right:.5rem}.swagger-ui .mr3{margin-right:1rem}.swagger-ui .mr4{margin-right:2rem}.swagger-ui .mr5{margin-right:4rem}.swagger-ui .mr6{margin-right:8rem}.swagger-ui .mr7{margin-right:16rem}.swagger-ui .mb0{margin-bottom:0}.swagger-ui .mb1{margin-bottom:.25rem}.swagger-ui .mb2{margin-bottom:.5rem}.swagger-ui .mb3{margin-bottom:1rem}.swagger-ui .mb4{margin-bottom:2rem}.swagger-ui .mb5{margin-bottom:4rem}.swagger-ui .mb6{margin-bottom:8rem}.swagger-ui .mb7{margin-bottom:16rem}.swagger-ui .mt0{margin-top:0}.swagger-ui .mt1{margin-top:.25rem}.swagger-ui .mt2{margin-top:.5rem}.swagger-ui .mt3{margin-top:1rem}.swagger-ui .mt4{margin-top:2rem}.swagger-ui .mt5{margin-top:4rem}.swagger-ui .mt6{margin-top:8rem}.swagger-ui .mt7{margin-top:16rem}.swagger-ui .mv0{margin-bottom:0;margin-top:0}.swagger-ui .mv1{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0{margin-left:0;margin-right:0}.swagger-ui .mh1{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7{margin-left:16rem;margin-right:16rem}@media screen and (min-width:30em){.swagger-ui .pa0-ns{padding:0}.swagger-ui .pa1-ns{padding:.25rem}.swagger-ui .pa2-ns{padding:.5rem}.swagger-ui .pa3-ns{padding:1rem}.swagger-ui .pa4-ns{padding:2rem}.swagger-ui .pa5-ns{padding:4rem}.swagger-ui .pa6-ns{padding:8rem}.swagger-ui .pa7-ns{padding:16rem}.swagger-ui .pl0-ns{padding-left:0}.swagger-ui .pl1-ns{padding-left:.25rem}.swagger-ui .pl2-ns{padding-left:.5rem}.swagger-ui .pl3-ns{padding-left:1rem}.swagger-ui .pl4-ns{padding-left:2rem}.swagger-ui .pl5-ns{padding-left:4rem}.swagger-ui .pl6-ns{padding-left:8rem}.swagger-ui .pl7-ns{padding-left:16rem}.swagger-ui .pr0-ns{padding-right:0}.swagger-ui .pr1-ns{padding-right:.25rem}.swagger-ui .pr2-ns{padding-right:.5rem}.swagger-ui .pr3-ns{padding-right:1rem}.swagger-ui .pr4-ns{padding-right:2rem}.swagger-ui .pr5-ns{padding-right:4rem}.swagger-ui .pr6-ns{padding-right:8rem}.swagger-ui .pr7-ns{padding-right:16rem}.swagger-ui .pb0-ns{padding-bottom:0}.swagger-ui .pb1-ns{padding-bottom:.25rem}.swagger-ui .pb2-ns{padding-bottom:.5rem}.swagger-ui .pb3-ns{padding-bottom:1rem}.swagger-ui .pb4-ns{padding-bottom:2rem}.swagger-ui .pb5-ns{padding-bottom:4rem}.swagger-ui .pb6-ns{padding-bottom:8rem}.swagger-ui .pb7-ns{padding-bottom:16rem}.swagger-ui .pt0-ns{padding-top:0}.swagger-ui .pt1-ns{padding-top:.25rem}.swagger-ui .pt2-ns{padding-top:.5rem}.swagger-ui .pt3-ns{padding-top:1rem}.swagger-ui .pt4-ns{padding-top:2rem}.swagger-ui .pt5-ns{padding-top:4rem}.swagger-ui .pt6-ns{padding-top:8rem}.swagger-ui .pt7-ns{padding-top:16rem}.swagger-ui .pv0-ns{padding-bottom:0;padding-top:0}.swagger-ui .pv1-ns{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-ns{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-ns{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-ns{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-ns{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-ns{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-ns{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-ns{padding-left:0;padding-right:0}.swagger-ui .ph1-ns{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-ns{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-ns{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-ns{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-ns{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-ns{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-ns{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-ns{margin:0}.swagger-ui .ma1-ns{margin:.25rem}.swagger-ui .ma2-ns{margin:.5rem}.swagger-ui .ma3-ns{margin:1rem}.swagger-ui .ma4-ns{margin:2rem}.swagger-ui .ma5-ns{margin:4rem}.swagger-ui .ma6-ns{margin:8rem}.swagger-ui .ma7-ns{margin:16rem}.swagger-ui .ml0-ns{margin-left:0}.swagger-ui .ml1-ns{margin-left:.25rem}.swagger-ui .ml2-ns{margin-left:.5rem}.swagger-ui .ml3-ns{margin-left:1rem}.swagger-ui .ml4-ns{margin-left:2rem}.swagger-ui .ml5-ns{margin-left:4rem}.swagger-ui .ml6-ns{margin-left:8rem}.swagger-ui .ml7-ns{margin-left:16rem}.swagger-ui .mr0-ns{margin-right:0}.swagger-ui .mr1-ns{margin-right:.25rem}.swagger-ui .mr2-ns{margin-right:.5rem}.swagger-ui .mr3-ns{margin-right:1rem}.swagger-ui .mr4-ns{margin-right:2rem}.swagger-ui .mr5-ns{margin-right:4rem}.swagger-ui .mr6-ns{margin-right:8rem}.swagger-ui .mr7-ns{margin-right:16rem}.swagger-ui .mb0-ns{margin-bottom:0}.swagger-ui .mb1-ns{margin-bottom:.25rem}.swagger-ui .mb2-ns{margin-bottom:.5rem}.swagger-ui .mb3-ns{margin-bottom:1rem}.swagger-ui .mb4-ns{margin-bottom:2rem}.swagger-ui .mb5-ns{margin-bottom:4rem}.swagger-ui .mb6-ns{margin-bottom:8rem}.swagger-ui .mb7-ns{margin-bottom:16rem}.swagger-ui .mt0-ns{margin-top:0}.swagger-ui .mt1-ns{margin-top:.25rem}.swagger-ui .mt2-ns{margin-top:.5rem}.swagger-ui .mt3-ns{margin-top:1rem}.swagger-ui .mt4-ns{margin-top:2rem}.swagger-ui .mt5-ns{margin-top:4rem}.swagger-ui .mt6-ns{margin-top:8rem}.swagger-ui .mt7-ns{margin-top:16rem}.swagger-ui .mv0-ns{margin-bottom:0;margin-top:0}.swagger-ui .mv1-ns{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-ns{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-ns{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-ns{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-ns{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-ns{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-ns{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-ns{margin-left:0;margin-right:0}.swagger-ui .mh1-ns{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-ns{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-ns{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-ns{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-ns{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-ns{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-ns{margin-left:16rem;margin-right:16rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .pa0-m{padding:0}.swagger-ui .pa1-m{padding:.25rem}.swagger-ui .pa2-m{padding:.5rem}.swagger-ui .pa3-m{padding:1rem}.swagger-ui .pa4-m{padding:2rem}.swagger-ui .pa5-m{padding:4rem}.swagger-ui .pa6-m{padding:8rem}.swagger-ui .pa7-m{padding:16rem}.swagger-ui .pl0-m{padding-left:0}.swagger-ui .pl1-m{padding-left:.25rem}.swagger-ui .pl2-m{padding-left:.5rem}.swagger-ui .pl3-m{padding-left:1rem}.swagger-ui .pl4-m{padding-left:2rem}.swagger-ui .pl5-m{padding-left:4rem}.swagger-ui .pl6-m{padding-left:8rem}.swagger-ui .pl7-m{padding-left:16rem}.swagger-ui .pr0-m{padding-right:0}.swagger-ui .pr1-m{padding-right:.25rem}.swagger-ui .pr2-m{padding-right:.5rem}.swagger-ui .pr3-m{padding-right:1rem}.swagger-ui .pr4-m{padding-right:2rem}.swagger-ui .pr5-m{padding-right:4rem}.swagger-ui .pr6-m{padding-right:8rem}.swagger-ui .pr7-m{padding-right:16rem}.swagger-ui .pb0-m{padding-bottom:0}.swagger-ui .pb1-m{padding-bottom:.25rem}.swagger-ui .pb2-m{padding-bottom:.5rem}.swagger-ui .pb3-m{padding-bottom:1rem}.swagger-ui .pb4-m{padding-bottom:2rem}.swagger-ui .pb5-m{padding-bottom:4rem}.swagger-ui .pb6-m{padding-bottom:8rem}.swagger-ui .pb7-m{padding-bottom:16rem}.swagger-ui .pt0-m{padding-top:0}.swagger-ui .pt1-m{padding-top:.25rem}.swagger-ui .pt2-m{padding-top:.5rem}.swagger-ui .pt3-m{padding-top:1rem}.swagger-ui .pt4-m{padding-top:2rem}.swagger-ui .pt5-m{padding-top:4rem}.swagger-ui .pt6-m{padding-top:8rem}.swagger-ui .pt7-m{padding-top:16rem}.swagger-ui .pv0-m{padding-bottom:0;padding-top:0}.swagger-ui .pv1-m{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-m{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-m{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-m{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-m{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-m{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-m{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-m{padding-left:0;padding-right:0}.swagger-ui .ph1-m{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-m{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-m{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-m{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-m{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-m{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-m{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-m{margin:0}.swagger-ui .ma1-m{margin:.25rem}.swagger-ui .ma2-m{margin:.5rem}.swagger-ui .ma3-m{margin:1rem}.swagger-ui .ma4-m{margin:2rem}.swagger-ui .ma5-m{margin:4rem}.swagger-ui .ma6-m{margin:8rem}.swagger-ui .ma7-m{margin:16rem}.swagger-ui .ml0-m{margin-left:0}.swagger-ui .ml1-m{margin-left:.25rem}.swagger-ui .ml2-m{margin-left:.5rem}.swagger-ui .ml3-m{margin-left:1rem}.swagger-ui .ml4-m{margin-left:2rem}.swagger-ui .ml5-m{margin-left:4rem}.swagger-ui .ml6-m{margin-left:8rem}.swagger-ui .ml7-m{margin-left:16rem}.swagger-ui .mr0-m{margin-right:0}.swagger-ui .mr1-m{margin-right:.25rem}.swagger-ui .mr2-m{margin-right:.5rem}.swagger-ui .mr3-m{margin-right:1rem}.swagger-ui .mr4-m{margin-right:2rem}.swagger-ui .mr5-m{margin-right:4rem}.swagger-ui .mr6-m{margin-right:8rem}.swagger-ui .mr7-m{margin-right:16rem}.swagger-ui .mb0-m{margin-bottom:0}.swagger-ui .mb1-m{margin-bottom:.25rem}.swagger-ui .mb2-m{margin-bottom:.5rem}.swagger-ui .mb3-m{margin-bottom:1rem}.swagger-ui .mb4-m{margin-bottom:2rem}.swagger-ui .mb5-m{margin-bottom:4rem}.swagger-ui .mb6-m{margin-bottom:8rem}.swagger-ui .mb7-m{margin-bottom:16rem}.swagger-ui .mt0-m{margin-top:0}.swagger-ui .mt1-m{margin-top:.25rem}.swagger-ui .mt2-m{margin-top:.5rem}.swagger-ui .mt3-m{margin-top:1rem}.swagger-ui .mt4-m{margin-top:2rem}.swagger-ui .mt5-m{margin-top:4rem}.swagger-ui .mt6-m{margin-top:8rem}.swagger-ui .mt7-m{margin-top:16rem}.swagger-ui .mv0-m{margin-bottom:0;margin-top:0}.swagger-ui .mv1-m{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-m{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-m{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-m{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-m{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-m{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-m{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-m{margin-left:0;margin-right:0}.swagger-ui .mh1-m{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-m{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-m{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-m{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-m{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-m{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-m{margin-left:16rem;margin-right:16rem}}@media screen and (min-width:60em){.swagger-ui .pa0-l{padding:0}.swagger-ui .pa1-l{padding:.25rem}.swagger-ui .pa2-l{padding:.5rem}.swagger-ui .pa3-l{padding:1rem}.swagger-ui .pa4-l{padding:2rem}.swagger-ui .pa5-l{padding:4rem}.swagger-ui .pa6-l{padding:8rem}.swagger-ui .pa7-l{padding:16rem}.swagger-ui .pl0-l{padding-left:0}.swagger-ui .pl1-l{padding-left:.25rem}.swagger-ui .pl2-l{padding-left:.5rem}.swagger-ui .pl3-l{padding-left:1rem}.swagger-ui .pl4-l{padding-left:2rem}.swagger-ui .pl5-l{padding-left:4rem}.swagger-ui .pl6-l{padding-left:8rem}.swagger-ui .pl7-l{padding-left:16rem}.swagger-ui .pr0-l{padding-right:0}.swagger-ui .pr1-l{padding-right:.25rem}.swagger-ui .pr2-l{padding-right:.5rem}.swagger-ui .pr3-l{padding-right:1rem}.swagger-ui .pr4-l{padding-right:2rem}.swagger-ui .pr5-l{padding-right:4rem}.swagger-ui .pr6-l{padding-right:8rem}.swagger-ui .pr7-l{padding-right:16rem}.swagger-ui .pb0-l{padding-bottom:0}.swagger-ui .pb1-l{padding-bottom:.25rem}.swagger-ui .pb2-l{padding-bottom:.5rem}.swagger-ui .pb3-l{padding-bottom:1rem}.swagger-ui .pb4-l{padding-bottom:2rem}.swagger-ui .pb5-l{padding-bottom:4rem}.swagger-ui .pb6-l{padding-bottom:8rem}.swagger-ui .pb7-l{padding-bottom:16rem}.swagger-ui .pt0-l{padding-top:0}.swagger-ui .pt1-l{padding-top:.25rem}.swagger-ui .pt2-l{padding-top:.5rem}.swagger-ui .pt3-l{padding-top:1rem}.swagger-ui .pt4-l{padding-top:2rem}.swagger-ui .pt5-l{padding-top:4rem}.swagger-ui .pt6-l{padding-top:8rem}.swagger-ui .pt7-l{padding-top:16rem}.swagger-ui .pv0-l{padding-bottom:0;padding-top:0}.swagger-ui .pv1-l{padding-bottom:.25rem;padding-top:.25rem}.swagger-ui .pv2-l{padding-bottom:.5rem;padding-top:.5rem}.swagger-ui .pv3-l{padding-bottom:1rem;padding-top:1rem}.swagger-ui .pv4-l{padding-bottom:2rem;padding-top:2rem}.swagger-ui .pv5-l{padding-bottom:4rem;padding-top:4rem}.swagger-ui .pv6-l{padding-bottom:8rem;padding-top:8rem}.swagger-ui .pv7-l{padding-bottom:16rem;padding-top:16rem}.swagger-ui .ph0-l{padding-left:0;padding-right:0}.swagger-ui .ph1-l{padding-left:.25rem;padding-right:.25rem}.swagger-ui .ph2-l{padding-left:.5rem;padding-right:.5rem}.swagger-ui .ph3-l{padding-left:1rem;padding-right:1rem}.swagger-ui .ph4-l{padding-left:2rem;padding-right:2rem}.swagger-ui .ph5-l{padding-left:4rem;padding-right:4rem}.swagger-ui .ph6-l{padding-left:8rem;padding-right:8rem}.swagger-ui .ph7-l{padding-left:16rem;padding-right:16rem}.swagger-ui .ma0-l{margin:0}.swagger-ui .ma1-l{margin:.25rem}.swagger-ui .ma2-l{margin:.5rem}.swagger-ui .ma3-l{margin:1rem}.swagger-ui .ma4-l{margin:2rem}.swagger-ui .ma5-l{margin:4rem}.swagger-ui .ma6-l{margin:8rem}.swagger-ui .ma7-l{margin:16rem}.swagger-ui .ml0-l{margin-left:0}.swagger-ui .ml1-l{margin-left:.25rem}.swagger-ui .ml2-l{margin-left:.5rem}.swagger-ui .ml3-l{margin-left:1rem}.swagger-ui .ml4-l{margin-left:2rem}.swagger-ui .ml5-l{margin-left:4rem}.swagger-ui .ml6-l{margin-left:8rem}.swagger-ui .ml7-l{margin-left:16rem}.swagger-ui .mr0-l{margin-right:0}.swagger-ui .mr1-l{margin-right:.25rem}.swagger-ui .mr2-l{margin-right:.5rem}.swagger-ui .mr3-l{margin-right:1rem}.swagger-ui .mr4-l{margin-right:2rem}.swagger-ui .mr5-l{margin-right:4rem}.swagger-ui .mr6-l{margin-right:8rem}.swagger-ui .mr7-l{margin-right:16rem}.swagger-ui .mb0-l{margin-bottom:0}.swagger-ui .mb1-l{margin-bottom:.25rem}.swagger-ui .mb2-l{margin-bottom:.5rem}.swagger-ui .mb3-l{margin-bottom:1rem}.swagger-ui .mb4-l{margin-bottom:2rem}.swagger-ui .mb5-l{margin-bottom:4rem}.swagger-ui .mb6-l{margin-bottom:8rem}.swagger-ui .mb7-l{margin-bottom:16rem}.swagger-ui .mt0-l{margin-top:0}.swagger-ui .mt1-l{margin-top:.25rem}.swagger-ui .mt2-l{margin-top:.5rem}.swagger-ui .mt3-l{margin-top:1rem}.swagger-ui .mt4-l{margin-top:2rem}.swagger-ui .mt5-l{margin-top:4rem}.swagger-ui .mt6-l{margin-top:8rem}.swagger-ui .mt7-l{margin-top:16rem}.swagger-ui .mv0-l{margin-bottom:0;margin-top:0}.swagger-ui .mv1-l{margin-bottom:.25rem;margin-top:.25rem}.swagger-ui .mv2-l{margin-bottom:.5rem;margin-top:.5rem}.swagger-ui .mv3-l{margin-bottom:1rem;margin-top:1rem}.swagger-ui .mv4-l{margin-bottom:2rem;margin-top:2rem}.swagger-ui .mv5-l{margin-bottom:4rem;margin-top:4rem}.swagger-ui .mv6-l{margin-bottom:8rem;margin-top:8rem}.swagger-ui .mv7-l{margin-bottom:16rem;margin-top:16rem}.swagger-ui .mh0-l{margin-left:0;margin-right:0}.swagger-ui .mh1-l{margin-left:.25rem;margin-right:.25rem}.swagger-ui .mh2-l{margin-left:.5rem;margin-right:.5rem}.swagger-ui .mh3-l{margin-left:1rem;margin-right:1rem}.swagger-ui .mh4-l{margin-left:2rem;margin-right:2rem}.swagger-ui .mh5-l{margin-left:4rem;margin-right:4rem}.swagger-ui .mh6-l{margin-left:8rem;margin-right:8rem}.swagger-ui .mh7-l{margin-left:16rem;margin-right:16rem}}.swagger-ui .na1{margin:-.25rem}.swagger-ui .na2{margin:-.5rem}.swagger-ui .na3{margin:-1rem}.swagger-ui .na4{margin:-2rem}.swagger-ui .na5{margin:-4rem}.swagger-ui .na6{margin:-8rem}.swagger-ui .na7{margin:-16rem}.swagger-ui .nl1{margin-left:-.25rem}.swagger-ui .nl2{margin-left:-.5rem}.swagger-ui .nl3{margin-left:-1rem}.swagger-ui .nl4{margin-left:-2rem}.swagger-ui .nl5{margin-left:-4rem}.swagger-ui .nl6{margin-left:-8rem}.swagger-ui .nl7{margin-left:-16rem}.swagger-ui .nr1{margin-right:-.25rem}.swagger-ui .nr2{margin-right:-.5rem}.swagger-ui .nr3{margin-right:-1rem}.swagger-ui .nr4{margin-right:-2rem}.swagger-ui .nr5{margin-right:-4rem}.swagger-ui .nr6{margin-right:-8rem}.swagger-ui .nr7{margin-right:-16rem}.swagger-ui .nb1{margin-bottom:-.25rem}.swagger-ui .nb2{margin-bottom:-.5rem}.swagger-ui .nb3{margin-bottom:-1rem}.swagger-ui .nb4{margin-bottom:-2rem}.swagger-ui .nb5{margin-bottom:-4rem}.swagger-ui .nb6{margin-bottom:-8rem}.swagger-ui .nb7{margin-bottom:-16rem}.swagger-ui .nt1{margin-top:-.25rem}.swagger-ui .nt2{margin-top:-.5rem}.swagger-ui .nt3{margin-top:-1rem}.swagger-ui .nt4{margin-top:-2rem}.swagger-ui .nt5{margin-top:-4rem}.swagger-ui .nt6{margin-top:-8rem}.swagger-ui .nt7{margin-top:-16rem}@media screen and (min-width:30em){.swagger-ui .na1-ns{margin:-.25rem}.swagger-ui .na2-ns{margin:-.5rem}.swagger-ui .na3-ns{margin:-1rem}.swagger-ui .na4-ns{margin:-2rem}.swagger-ui .na5-ns{margin:-4rem}.swagger-ui .na6-ns{margin:-8rem}.swagger-ui .na7-ns{margin:-16rem}.swagger-ui .nl1-ns{margin-left:-.25rem}.swagger-ui .nl2-ns{margin-left:-.5rem}.swagger-ui .nl3-ns{margin-left:-1rem}.swagger-ui .nl4-ns{margin-left:-2rem}.swagger-ui .nl5-ns{margin-left:-4rem}.swagger-ui .nl6-ns{margin-left:-8rem}.swagger-ui .nl7-ns{margin-left:-16rem}.swagger-ui .nr1-ns{margin-right:-.25rem}.swagger-ui .nr2-ns{margin-right:-.5rem}.swagger-ui .nr3-ns{margin-right:-1rem}.swagger-ui .nr4-ns{margin-right:-2rem}.swagger-ui .nr5-ns{margin-right:-4rem}.swagger-ui .nr6-ns{margin-right:-8rem}.swagger-ui .nr7-ns{margin-right:-16rem}.swagger-ui .nb1-ns{margin-bottom:-.25rem}.swagger-ui .nb2-ns{margin-bottom:-.5rem}.swagger-ui .nb3-ns{margin-bottom:-1rem}.swagger-ui .nb4-ns{margin-bottom:-2rem}.swagger-ui .nb5-ns{margin-bottom:-4rem}.swagger-ui .nb6-ns{margin-bottom:-8rem}.swagger-ui .nb7-ns{margin-bottom:-16rem}.swagger-ui .nt1-ns{margin-top:-.25rem}.swagger-ui .nt2-ns{margin-top:-.5rem}.swagger-ui .nt3-ns{margin-top:-1rem}.swagger-ui .nt4-ns{margin-top:-2rem}.swagger-ui .nt5-ns{margin-top:-4rem}.swagger-ui .nt6-ns{margin-top:-8rem}.swagger-ui .nt7-ns{margin-top:-16rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .na1-m{margin:-.25rem}.swagger-ui .na2-m{margin:-.5rem}.swagger-ui .na3-m{margin:-1rem}.swagger-ui .na4-m{margin:-2rem}.swagger-ui .na5-m{margin:-4rem}.swagger-ui .na6-m{margin:-8rem}.swagger-ui .na7-m{margin:-16rem}.swagger-ui .nl1-m{margin-left:-.25rem}.swagger-ui .nl2-m{margin-left:-.5rem}.swagger-ui .nl3-m{margin-left:-1rem}.swagger-ui .nl4-m{margin-left:-2rem}.swagger-ui .nl5-m{margin-left:-4rem}.swagger-ui .nl6-m{margin-left:-8rem}.swagger-ui .nl7-m{margin-left:-16rem}.swagger-ui .nr1-m{margin-right:-.25rem}.swagger-ui .nr2-m{margin-right:-.5rem}.swagger-ui .nr3-m{margin-right:-1rem}.swagger-ui .nr4-m{margin-right:-2rem}.swagger-ui .nr5-m{margin-right:-4rem}.swagger-ui .nr6-m{margin-right:-8rem}.swagger-ui .nr7-m{margin-right:-16rem}.swagger-ui .nb1-m{margin-bottom:-.25rem}.swagger-ui .nb2-m{margin-bottom:-.5rem}.swagger-ui .nb3-m{margin-bottom:-1rem}.swagger-ui .nb4-m{margin-bottom:-2rem}.swagger-ui .nb5-m{margin-bottom:-4rem}.swagger-ui .nb6-m{margin-bottom:-8rem}.swagger-ui .nb7-m{margin-bottom:-16rem}.swagger-ui .nt1-m{margin-top:-.25rem}.swagger-ui .nt2-m{margin-top:-.5rem}.swagger-ui .nt3-m{margin-top:-1rem}.swagger-ui .nt4-m{margin-top:-2rem}.swagger-ui .nt5-m{margin-top:-4rem}.swagger-ui .nt6-m{margin-top:-8rem}.swagger-ui .nt7-m{margin-top:-16rem}}@media screen and (min-width:60em){.swagger-ui .na1-l{margin:-.25rem}.swagger-ui .na2-l{margin:-.5rem}.swagger-ui .na3-l{margin:-1rem}.swagger-ui .na4-l{margin:-2rem}.swagger-ui .na5-l{margin:-4rem}.swagger-ui .na6-l{margin:-8rem}.swagger-ui .na7-l{margin:-16rem}.swagger-ui .nl1-l{margin-left:-.25rem}.swagger-ui .nl2-l{margin-left:-.5rem}.swagger-ui .nl3-l{margin-left:-1rem}.swagger-ui .nl4-l{margin-left:-2rem}.swagger-ui .nl5-l{margin-left:-4rem}.swagger-ui .nl6-l{margin-left:-8rem}.swagger-ui .nl7-l{margin-left:-16rem}.swagger-ui .nr1-l{margin-right:-.25rem}.swagger-ui .nr2-l{margin-right:-.5rem}.swagger-ui .nr3-l{margin-right:-1rem}.swagger-ui .nr4-l{margin-right:-2rem}.swagger-ui .nr5-l{margin-right:-4rem}.swagger-ui .nr6-l{margin-right:-8rem}.swagger-ui .nr7-l{margin-right:-16rem}.swagger-ui .nb1-l{margin-bottom:-.25rem}.swagger-ui .nb2-l{margin-bottom:-.5rem}.swagger-ui .nb3-l{margin-bottom:-1rem}.swagger-ui .nb4-l{margin-bottom:-2rem}.swagger-ui .nb5-l{margin-bottom:-4rem}.swagger-ui .nb6-l{margin-bottom:-8rem}.swagger-ui .nb7-l{margin-bottom:-16rem}.swagger-ui .nt1-l{margin-top:-.25rem}.swagger-ui .nt2-l{margin-top:-.5rem}.swagger-ui .nt3-l{margin-top:-1rem}.swagger-ui .nt4-l{margin-top:-2rem}.swagger-ui .nt5-l{margin-top:-4rem}.swagger-ui .nt6-l{margin-top:-8rem}.swagger-ui .nt7-l{margin-top:-16rem}}.swagger-ui .collapse{border-collapse:collapse;border-spacing:0}.swagger-ui .striped--light-silver:nth-child(odd){background-color:#aaa}.swagger-ui .striped--moon-gray:nth-child(odd){background-color:#ccc}.swagger-ui .striped--light-gray:nth-child(odd){background-color:#eee}.swagger-ui .striped--near-white:nth-child(odd){background-color:#f4f4f4}.swagger-ui .stripe-light:nth-child(odd){background-color:hsla(0,0%,100%,.1)}.swagger-ui .stripe-dark:nth-child(odd){background-color:rgba(0,0,0,.1)}.swagger-ui .strike{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline{-webkit-text-decoration:none;text-decoration:none}@media screen and (min-width:30em){.swagger-ui .strike-ns{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-ns{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-ns{-webkit-text-decoration:none;text-decoration:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .strike-m{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-m{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-m{-webkit-text-decoration:none;text-decoration:none}}@media screen and (min-width:60em){.swagger-ui .strike-l{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .underline-l{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .no-underline-l{-webkit-text-decoration:none;text-decoration:none}}.swagger-ui .tl{text-align:left}.swagger-ui .tr{text-align:right}.swagger-ui .tc{text-align:center}.swagger-ui .tj{text-align:justify}@media screen and (min-width:30em){.swagger-ui .tl-ns{text-align:left}.swagger-ui .tr-ns{text-align:right}.swagger-ui .tc-ns{text-align:center}.swagger-ui .tj-ns{text-align:justify}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .tl-m{text-align:left}.swagger-ui .tr-m{text-align:right}.swagger-ui .tc-m{text-align:center}.swagger-ui .tj-m{text-align:justify}}@media screen and (min-width:60em){.swagger-ui .tl-l{text-align:left}.swagger-ui .tr-l{text-align:right}.swagger-ui .tc-l{text-align:center}.swagger-ui .tj-l{text-align:justify}}.swagger-ui .ttc{text-transform:capitalize}.swagger-ui .ttl{text-transform:lowercase}.swagger-ui .ttu{text-transform:uppercase}.swagger-ui .ttn{text-transform:none}@media screen and (min-width:30em){.swagger-ui .ttc-ns{text-transform:capitalize}.swagger-ui .ttl-ns{text-transform:lowercase}.swagger-ui .ttu-ns{text-transform:uppercase}.swagger-ui .ttn-ns{text-transform:none}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ttc-m{text-transform:capitalize}.swagger-ui .ttl-m{text-transform:lowercase}.swagger-ui .ttu-m{text-transform:uppercase}.swagger-ui .ttn-m{text-transform:none}}@media screen and (min-width:60em){.swagger-ui .ttc-l{text-transform:capitalize}.swagger-ui .ttl-l{text-transform:lowercase}.swagger-ui .ttu-l{text-transform:uppercase}.swagger-ui .ttn-l{text-transform:none}}.swagger-ui .f-6,.swagger-ui .f-headline{font-size:6rem}.swagger-ui .f-5,.swagger-ui .f-subheadline{font-size:5rem}.swagger-ui .f1{font-size:3rem}.swagger-ui .f2{font-size:2.25rem}.swagger-ui .f3{font-size:1.5rem}.swagger-ui .f4{font-size:1.25rem}.swagger-ui .f5{font-size:1rem}.swagger-ui .f6{font-size:.875rem}.swagger-ui .f7{font-size:.75rem}@media screen and (min-width:30em){.swagger-ui .f-6-ns,.swagger-ui .f-headline-ns{font-size:6rem}.swagger-ui .f-5-ns,.swagger-ui .f-subheadline-ns{font-size:5rem}.swagger-ui .f1-ns{font-size:3rem}.swagger-ui .f2-ns{font-size:2.25rem}.swagger-ui .f3-ns{font-size:1.5rem}.swagger-ui .f4-ns{font-size:1.25rem}.swagger-ui .f5-ns{font-size:1rem}.swagger-ui .f6-ns{font-size:.875rem}.swagger-ui .f7-ns{font-size:.75rem}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .f-6-m,.swagger-ui .f-headline-m{font-size:6rem}.swagger-ui .f-5-m,.swagger-ui .f-subheadline-m{font-size:5rem}.swagger-ui .f1-m{font-size:3rem}.swagger-ui .f2-m{font-size:2.25rem}.swagger-ui .f3-m{font-size:1.5rem}.swagger-ui .f4-m{font-size:1.25rem}.swagger-ui .f5-m{font-size:1rem}.swagger-ui .f6-m{font-size:.875rem}.swagger-ui .f7-m{font-size:.75rem}}@media screen and (min-width:60em){.swagger-ui .f-6-l,.swagger-ui .f-headline-l{font-size:6rem}.swagger-ui .f-5-l,.swagger-ui .f-subheadline-l{font-size:5rem}.swagger-ui .f1-l{font-size:3rem}.swagger-ui .f2-l{font-size:2.25rem}.swagger-ui .f3-l{font-size:1.5rem}.swagger-ui .f4-l{font-size:1.25rem}.swagger-ui .f5-l{font-size:1rem}.swagger-ui .f6-l{font-size:.875rem}.swagger-ui .f7-l{font-size:.75rem}}.swagger-ui .measure{max-width:30em}.swagger-ui .measure-wide{max-width:34em}.swagger-ui .measure-narrow{max-width:20em}.swagger-ui .indent{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media screen and (min-width:30em){.swagger-ui .measure-ns{max-width:30em}.swagger-ui .measure-wide-ns{max-width:34em}.swagger-ui .measure-narrow-ns{max-width:20em}.swagger-ui .indent-ns{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-ns{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-ns{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .measure-m{max-width:30em}.swagger-ui .measure-wide-m{max-width:34em}.swagger-ui .measure-narrow-m{max-width:20em}.swagger-ui .indent-m{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-m{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-m{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}@media screen and (min-width:60em){.swagger-ui .measure-l{max-width:30em}.swagger-ui .measure-wide-l{max-width:34em}.swagger-ui .measure-narrow-l{max-width:20em}.swagger-ui .indent-l{margin-bottom:0;margin-top:0;text-indent:1em}.swagger-ui .small-caps-l{font-feature-settings:"smcp";font-variant:small-caps}.swagger-ui .truncate-l{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}}.swagger-ui .overflow-container{overflow-y:scroll}.swagger-ui .center{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto{margin-right:auto}.swagger-ui .ml-auto{margin-left:auto}@media screen and (min-width:30em){.swagger-ui .center-ns{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-ns{margin-right:auto}.swagger-ui .ml-auto-ns{margin-left:auto}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .center-m{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-m{margin-right:auto}.swagger-ui .ml-auto-m{margin-left:auto}}@media screen and (min-width:60em){.swagger-ui .center-l{margin-left:auto;margin-right:auto}.swagger-ui .mr-auto-l{margin-right:auto}.swagger-ui .ml-auto-l{margin-left:auto}}.swagger-ui .clip{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}@media screen and (min-width:30em){.swagger-ui .clip-ns{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .clip-m{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}@media screen and (min-width:60em){.swagger-ui .clip-l{position:fixed!important;_position:absolute!important;clip:rect(1px 1px 1px 1px);clip:rect(1px,1px,1px,1px)}}.swagger-ui .ws-normal{white-space:normal}.swagger-ui .nowrap{white-space:nowrap}.swagger-ui .pre{white-space:pre}@media screen and (min-width:30em){.swagger-ui .ws-normal-ns{white-space:normal}.swagger-ui .nowrap-ns{white-space:nowrap}.swagger-ui .pre-ns{white-space:pre}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .ws-normal-m{white-space:normal}.swagger-ui .nowrap-m{white-space:nowrap}.swagger-ui .pre-m{white-space:pre}}@media screen and (min-width:60em){.swagger-ui .ws-normal-l{white-space:normal}.swagger-ui .nowrap-l{white-space:nowrap}.swagger-ui .pre-l{white-space:pre}}.swagger-ui .v-base{vertical-align:baseline}.swagger-ui .v-mid{vertical-align:middle}.swagger-ui .v-top{vertical-align:top}.swagger-ui .v-btm{vertical-align:bottom}@media screen and (min-width:30em){.swagger-ui .v-base-ns{vertical-align:baseline}.swagger-ui .v-mid-ns{vertical-align:middle}.swagger-ui .v-top-ns{vertical-align:top}.swagger-ui .v-btm-ns{vertical-align:bottom}}@media screen and (min-width:30em)and (max-width:60em){.swagger-ui .v-base-m{vertical-align:baseline}.swagger-ui .v-mid-m{vertical-align:middle}.swagger-ui .v-top-m{vertical-align:top}.swagger-ui .v-btm-m{vertical-align:bottom}}@media screen and (min-width:60em){.swagger-ui .v-base-l{vertical-align:baseline}.swagger-ui .v-mid-l{vertical-align:middle}.swagger-ui .v-top-l{vertical-align:top}.swagger-ui .v-btm-l{vertical-align:bottom}}.swagger-ui .dim{opacity:1;transition:opacity .15s ease-in}.swagger-ui .dim:focus,.swagger-ui .dim:hover{opacity:.5;transition:opacity .15s ease-in}.swagger-ui .dim:active{opacity:.8;transition:opacity .15s ease-out}.swagger-ui .glow{transition:opacity .15s ease-in}.swagger-ui .glow:focus,.swagger-ui .glow:hover{opacity:1;transition:opacity .15s ease-in}.swagger-ui .hide-child .child{opacity:0;transition:opacity .15s ease-in}.swagger-ui .hide-child:active .child,.swagger-ui .hide-child:focus .child,.swagger-ui .hide-child:hover .child{opacity:1;transition:opacity .15s ease-in}.swagger-ui .underline-hover:focus,.swagger-ui .underline-hover:hover{-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .grow{-moz-osx-font-smoothing:grayscale;backface-visibility:hidden;transform:translateZ(0);transition:transform .25s ease-out}.swagger-ui .grow:focus,.swagger-ui .grow:hover{transform:scale(1.05)}.swagger-ui .grow:active{transform:scale(.9)}.swagger-ui .grow-large{-moz-osx-font-smoothing:grayscale;backface-visibility:hidden;transform:translateZ(0);transition:transform .25s ease-in-out}.swagger-ui .grow-large:focus,.swagger-ui .grow-large:hover{transform:scale(1.2)}.swagger-ui .grow-large:active{transform:scale(.95)}.swagger-ui .pointer:hover{cursor:pointer}.swagger-ui .shadow-hover{cursor:pointer;position:relative;transition:all .5s cubic-bezier(.165,.84,.44,1)}.swagger-ui .shadow-hover:after{border-radius:inherit;box-shadow:0 0 16px 2px rgba(0,0,0,.2);content:"";height:100%;left:0;opacity:0;position:absolute;top:0;transition:opacity .5s cubic-bezier(.165,.84,.44,1);width:100%;z-index:-1}.swagger-ui .shadow-hover:focus:after,.swagger-ui .shadow-hover:hover:after{opacity:1}.swagger-ui .bg-animate,.swagger-ui .bg-animate:focus,.swagger-ui .bg-animate:hover{transition:background-color .15s ease-in-out}.swagger-ui .z-0{z-index:0}.swagger-ui .z-1{z-index:1}.swagger-ui .z-2{z-index:2}.swagger-ui .z-3{z-index:3}.swagger-ui .z-4{z-index:4}.swagger-ui .z-5{z-index:5}.swagger-ui .z-999{z-index:999}.swagger-ui .z-9999{z-index:9999}.swagger-ui .z-max{z-index:2147483647}.swagger-ui .z-inherit{z-index:inherit}.swagger-ui .z-initial,.swagger-ui .z-unset{z-index:auto}.swagger-ui .nested-copy-line-height ol,.swagger-ui .nested-copy-line-height p,.swagger-ui .nested-copy-line-height ul{line-height:1.5}.swagger-ui .nested-headline-line-height h1,.swagger-ui .nested-headline-line-height h2,.swagger-ui .nested-headline-line-height h3,.swagger-ui .nested-headline-line-height h4,.swagger-ui .nested-headline-line-height h5,.swagger-ui .nested-headline-line-height h6{line-height:1.25}.swagger-ui .nested-list-reset ol,.swagger-ui .nested-list-reset ul{list-style-type:none;margin-left:0;padding-left:0}.swagger-ui .nested-copy-indent p+p{margin-bottom:0;margin-top:0;text-indent:.1em}.swagger-ui .nested-copy-seperator p+p{margin-top:1.5em}.swagger-ui .nested-img img{display:block;max-width:100%;width:100%}.swagger-ui .nested-links a{color:#357edd;transition:color .15s ease-in}.swagger-ui .nested-links a:focus,.swagger-ui .nested-links a:hover{color:#96ccff;transition:color .15s ease-in}.swagger-ui .wrapper{box-sizing:border-box;margin:0 auto;max-width:1460px;padding:0 20px;width:100%}.swagger-ui .opblock-tag-section{display:flex;flex-direction:column}.swagger-ui .try-out.btn-group{display:flex;flex:.1 2 auto;padding:0}.swagger-ui .try-out__btn{margin-left:1.25rem}.swagger-ui .opblock-tag{align-items:center;border-bottom:1px solid rgba(59,65,81,.3);cursor:pointer;display:flex;padding:10px 20px 10px 10px;transition:all .2s}.swagger-ui .opblock-tag:hover{background:rgba(0,0,0,.02)}.swagger-ui .opblock-tag{color:#3b4151;font-family:sans-serif;font-size:24px;margin:0 0 5px}.swagger-ui .opblock-tag.no-desc span{flex:1}.swagger-ui .opblock-tag svg{transition:all .4s}.swagger-ui .opblock-tag small{color:#3b4151;flex:2;font-family:sans-serif;font-size:14px;font-weight:400;padding:0 10px}.swagger-ui .opblock-tag>div{flex:1 1 150px;font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:640px){.swagger-ui .opblock-tag small,.swagger-ui .opblock-tag>div{flex:1}}.swagger-ui .opblock-tag .info__externaldocs{text-align:right}.swagger-ui .parameter__type{color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;padding:5px 0}.swagger-ui .parameter-controls{margin-top:.75em}.swagger-ui .examples__title{display:block;font-size:1.1em;font-weight:700;margin-bottom:.75em}.swagger-ui .examples__section{margin-top:1.5em}.swagger-ui .examples__section-header{font-size:.9rem;font-weight:700;margin-bottom:.5rem}.swagger-ui .examples-select{display:inline-block;margin-bottom:.75em}.swagger-ui .examples-select .examples-select-element{width:100%}.swagger-ui .examples-select__section-label{font-size:.9rem;font-weight:700;margin-right:.5rem}.swagger-ui .example__section{margin-top:1.5em}.swagger-ui .example__section-header{font-size:.9rem;font-weight:700;margin-bottom:.5rem}.swagger-ui .view-line-link{cursor:pointer;margin:0 5px;position:relative;top:3px;transition:all .5s;width:20px}.swagger-ui .opblock{border:1px solid #000;border-radius:4px;box-shadow:0 0 3px rgba(0,0,0,.19);margin:0 0 15px}.swagger-ui .opblock .tab-header{display:flex;flex:1}.swagger-ui .opblock .tab-header .tab-item{cursor:pointer;padding:0 40px}.swagger-ui .opblock .tab-header .tab-item:first-of-type{padding:0 40px 0 0}.swagger-ui .opblock .tab-header .tab-item.active h4 span{position:relative}.swagger-ui .opblock .tab-header .tab-item.active h4 span:after{background:grey;bottom:-15px;content:"";height:4px;left:50%;position:absolute;transform:translateX(-50%);width:120%}.swagger-ui .opblock.is-open .opblock-summary{border-bottom:1px solid #000}.swagger-ui .opblock .opblock-section-header{align-items:center;background:hsla(0,0%,100%,.8);box-shadow:0 1px 2px rgba(0,0,0,.1);display:flex;min-height:50px;padding:8px 20px}.swagger-ui .opblock .opblock-section-header>label{align-items:center;color:#3b4151;display:flex;font-family:sans-serif;font-size:12px;font-weight:700;margin:0 0 0 auto}.swagger-ui .opblock .opblock-section-header>label>span{padding:0 10px 0 0}.swagger-ui .opblock .opblock-section-header h4{color:#3b4151;flex:1;font-family:sans-serif;font-size:14px;margin:0}.swagger-ui .opblock .opblock-summary-method{background:#000;border-radius:3px;color:#fff;font-family:sans-serif;font-size:14px;font-weight:700;min-width:80px;padding:6px 0;text-align:center;text-shadow:0 1px 0 rgba(0,0,0,.1)}@media(max-width:768px){.swagger-ui .opblock .opblock-summary-method{font-size:12px}}.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{align-items:center;color:#3b4151;display:flex;font-family:monospace;font-size:16px;font-weight:600;word-break:break-word}@media(max-width:768px){.swagger-ui .opblock .opblock-summary-operation-id,.swagger-ui .opblock .opblock-summary-path,.swagger-ui .opblock .opblock-summary-path__deprecated{font-size:12px}}.swagger-ui .opblock .opblock-summary-path{flex-shrink:1}@media(max-width:640px){.swagger-ui .opblock .opblock-summary-path{max-width:100%}}.swagger-ui .opblock .opblock-summary-path__deprecated{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .opblock .opblock-summary-operation-id{font-size:14px}.swagger-ui .opblock .opblock-summary-description{color:#3b4151;font-family:sans-serif;font-size:13px;word-break:break-word}.swagger-ui .opblock .opblock-summary-path-description-wrapper{align-items:center;display:flex;flex-direction:row;flex-grow:1;flex-wrap:wrap;gap:0 10px;padding:0 10px}@media(max-width:550px){.swagger-ui .opblock .opblock-summary-path-description-wrapper{align-items:flex-start;flex-direction:column}}.swagger-ui .opblock .opblock-summary{align-items:center;cursor:pointer;display:flex;padding:5px}.swagger-ui .opblock .opblock-summary .view-line-link{cursor:pointer;margin:0;position:relative;top:2px;transition:all .5s;width:0}.swagger-ui .opblock .opblock-summary:hover .view-line-link{margin:0 5px;width:18px}.swagger-ui .opblock .opblock-summary:hover .view-line-link.copy-to-clipboard{width:24px}.swagger-ui .opblock.opblock-post{background:rgba(73,204,144,.1);border-color:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary-method{background:#49cc90}.swagger-ui .opblock.opblock-post .opblock-summary{border-color:#49cc90}.swagger-ui .opblock.opblock-post .tab-header .tab-item.active h4 span:after{background:#49cc90}.swagger-ui .opblock.opblock-put{background:rgba(252,161,48,.1);border-color:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary-method{background:#fca130}.swagger-ui .opblock.opblock-put .opblock-summary{border-color:#fca130}.swagger-ui .opblock.opblock-put .tab-header .tab-item.active h4 span:after{background:#fca130}.swagger-ui .opblock.opblock-delete{background:rgba(249,62,62,.1);border-color:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary-method{background:#f93e3e}.swagger-ui .opblock.opblock-delete .opblock-summary{border-color:#f93e3e}.swagger-ui .opblock.opblock-delete .tab-header .tab-item.active h4 span:after{background:#f93e3e}.swagger-ui .opblock.opblock-get{background:rgba(97,175,254,.1);border-color:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary-method{background:#61affe}.swagger-ui .opblock.opblock-get .opblock-summary{border-color:#61affe}.swagger-ui .opblock.opblock-get .tab-header .tab-item.active h4 span:after{background:#61affe}.swagger-ui .opblock.opblock-patch{background:rgba(80,227,194,.1);border-color:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary-method{background:#50e3c2}.swagger-ui .opblock.opblock-patch .opblock-summary{border-color:#50e3c2}.swagger-ui .opblock.opblock-patch .tab-header .tab-item.active h4 span:after{background:#50e3c2}.swagger-ui .opblock.opblock-head{background:rgba(144,18,254,.1);border-color:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary-method{background:#9012fe}.swagger-ui .opblock.opblock-head .opblock-summary{border-color:#9012fe}.swagger-ui .opblock.opblock-head .tab-header .tab-item.active h4 span:after{background:#9012fe}.swagger-ui .opblock.opblock-options{background:rgba(13,90,167,.1);border-color:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary-method{background:#0d5aa7}.swagger-ui .opblock.opblock-options .opblock-summary{border-color:#0d5aa7}.swagger-ui .opblock.opblock-options .tab-header .tab-item.active h4 span:after{background:#0d5aa7}.swagger-ui .opblock.opblock-deprecated{background:hsla(0,0%,92%,.1);border-color:#ebebeb;opacity:.6}.swagger-ui .opblock.opblock-deprecated .opblock-summary-method{background:#ebebeb}.swagger-ui .opblock.opblock-deprecated .opblock-summary{border-color:#ebebeb}.swagger-ui .opblock.opblock-deprecated .tab-header .tab-item.active h4 span:after{background:#ebebeb}.swagger-ui .opblock .opblock-schemes{padding:8px 20px}.swagger-ui .opblock .opblock-schemes .schemes-title{padding:0 10px 0 0}.swagger-ui .filter .operation-filter-input{border:2px solid #d8dde7;margin:20px 0;padding:10px;width:100%}.swagger-ui .download-url-wrapper .failed,.swagger-ui .filter .failed{color:red}.swagger-ui .download-url-wrapper .loading,.swagger-ui .filter .loading{color:#aaa}.swagger-ui .model-example{margin-top:1em}.swagger-ui .model-example .model-container{overflow-x:auto;width:100%}.swagger-ui .model-example .model-container .model-hint:not(.model-hint--embedded){top:-1.15em}.swagger-ui .tab{display:flex;list-style:none;padding:0}.swagger-ui .tab li{color:#3b4151;cursor:pointer;font-family:sans-serif;font-size:12px;min-width:60px;padding:0}.swagger-ui .tab li:first-of-type{padding-left:0;padding-right:12px;position:relative}.swagger-ui .tab li:first-of-type:after{background:rgba(0,0,0,.2);content:"";height:100%;position:absolute;right:6px;top:0;width:1px}.swagger-ui .tab li.active{font-weight:700}.swagger-ui .tab li button.tablinks{background:none;border:0;color:inherit;font-family:inherit;font-weight:inherit;padding:0}.swagger-ui .opblock-description-wrapper,.swagger-ui .opblock-external-docs-wrapper,.swagger-ui .opblock-title_normal{color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px;padding:15px 20px}.swagger-ui .opblock-description-wrapper h4,.swagger-ui .opblock-external-docs-wrapper h4,.swagger-ui .opblock-title_normal h4{color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px}.swagger-ui .opblock-description-wrapper p,.swagger-ui .opblock-external-docs-wrapper p,.swagger-ui .opblock-title_normal p{color:#3b4151;font-family:sans-serif;font-size:14px;margin:0}.swagger-ui .opblock-external-docs-wrapper h4{padding-left:0}.swagger-ui .execute-wrapper{padding:20px;text-align:right}.swagger-ui .execute-wrapper .btn{padding:8px 40px;width:100%}.swagger-ui .body-param-options{display:flex;flex-direction:column}.swagger-ui .body-param-options .body-param-edit{padding:10px 0}.swagger-ui .body-param-options label{padding:8px 0}.swagger-ui .body-param-options label select{margin:3px 0 0}.swagger-ui .responses-inner{padding:20px}.swagger-ui .responses-inner h4,.swagger-ui .responses-inner h5{color:#3b4151;font-family:sans-serif;font-size:12px;margin:10px 0 5px}.swagger-ui .responses-inner .curl{max-height:400px;min-height:6em;overflow-y:auto}.swagger-ui .response-col_status{color:#3b4151;font-family:sans-serif;font-size:14px}.swagger-ui .response-col_status .response-undocumented{color:#909090;font-family:monospace;font-size:11px;font-weight:600}.swagger-ui .response-col_links{color:#3b4151;font-family:sans-serif;font-size:14px;max-width:40em;padding-left:2em}.swagger-ui .response-col_links .response-undocumented{color:#909090;font-family:monospace;font-size:11px;font-weight:600}.swagger-ui .response-col_links .operation-link{margin-bottom:1.5em}.swagger-ui .response-col_links .operation-link .description{margin-bottom:.5em}.swagger-ui .opblock-body .opblock-loading-animation{display:block;margin:3em auto}.swagger-ui .opblock-body pre.microlight{background:#333;border-radius:4px;font-size:12px;hyphens:auto;margin:0;padding:10px;white-space:pre-wrap;word-break:break-all;word-break:break-word;word-wrap:break-word;color:#fff;font-family:monospace;font-weight:600}.swagger-ui .opblock-body pre.microlight .headerline{display:block}.swagger-ui .highlight-code{position:relative}.swagger-ui .highlight-code>.microlight{max-height:400px;min-height:6em;overflow-y:auto}.swagger-ui .highlight-code>.microlight code{white-space:pre-wrap!important;word-break:break-all}.swagger-ui .curl-command{position:relative}.swagger-ui .download-contents{align-items:center;background:#7d8293;border:none;border-radius:4px;bottom:10px;color:#fff;display:flex;font-family:sans-serif;font-size:14px;font-weight:600;height:30px;justify-content:center;padding:5px;position:absolute;right:10px;text-align:center}.swagger-ui .scheme-container{background:#fff;box-shadow:0 1px 2px 0 rgba(0,0,0,.15);margin:0 0 20px;padding:30px 0}.swagger-ui .scheme-container .schemes{align-items:flex-end;display:flex;flex-wrap:wrap;gap:10px;justify-content:space-between}.swagger-ui .scheme-container .schemes>.schemes-server-container{display:flex;flex-wrap:wrap;gap:10px}.swagger-ui .scheme-container .schemes>.schemes-server-container>label{color:#3b4151;display:flex;flex-direction:column;font-family:sans-serif;font-size:12px;font-weight:700;margin:-20px 15px 0 0}.swagger-ui .scheme-container .schemes>.schemes-server-container>label select{min-width:130px;text-transform:uppercase}.swagger-ui .scheme-container .schemes:not(:has(.schemes-server-container)){justify-content:flex-end}.swagger-ui .scheme-container .schemes .auth-wrapper{flex:none;justify-content:start}.swagger-ui .scheme-container .schemes .auth-wrapper .authorize{display:flex;flex-wrap:nowrap;margin:0;padding-right:20px}.swagger-ui .loading-container{align-items:center;display:flex;flex-direction:column;justify-content:center;margin-top:1em;min-height:1px;padding:40px 0 60px}.swagger-ui .loading-container .loading{position:relative}.swagger-ui .loading-container .loading:after{color:#3b4151;content:"loading";font-family:sans-serif;font-size:10px;font-weight:700;left:50%;position:absolute;text-transform:uppercase;top:50%;transform:translate(-50%,-50%)}.swagger-ui .loading-container .loading:before{animation:rotation 1s linear infinite,opacity .5s;backface-visibility:hidden;border:2px solid rgba(85,85,85,.1);border-radius:100%;border-top-color:rgba(0,0,0,.6);content:"";display:block;height:60px;left:50%;margin:-30px;opacity:1;position:absolute;top:50%;width:60px}@keyframes rotation{to{transform:rotate(1turn)}}.swagger-ui .response-controls{display:flex;padding-top:1em}.swagger-ui .response-control-media-type{margin-right:1em}.swagger-ui .response-control-media-type--accept-controller select{border-color:green}.swagger-ui .response-control-media-type__accept-message{color:green;font-size:.7em}.swagger-ui .response-control-examples__title,.swagger-ui .response-control-media-type__title{display:block;font-size:.7em;margin-bottom:.2em}@keyframes blinker{50%{opacity:0}}.swagger-ui .hidden{display:none}.swagger-ui .no-margin{border:none;height:auto;margin:0;padding:0}.swagger-ui .float-right{float:right}.swagger-ui .svg-assets{height:0;position:absolute;width:0}.swagger-ui section h3{color:#3b4151;font-family:sans-serif}.swagger-ui a.nostyle{display:inline}.swagger-ui a.nostyle,.swagger-ui a.nostyle:visited{color:inherit;cursor:pointer;text-decoration:inherit}.swagger-ui .fallback{color:#aaa;padding:1em}.swagger-ui .version-pragma{height:100%;padding:5em 0}.swagger-ui .version-pragma__message{display:flex;font-size:1.2em;height:100%;justify-content:center;line-height:1.5em;padding:0 .6em;text-align:center}.swagger-ui .version-pragma__message>div{flex:1;max-width:55ch}.swagger-ui .version-pragma__message code{background-color:#dedede;padding:4px 4px 2px;white-space:pre}.swagger-ui .opblock-link{font-weight:400}.swagger-ui .opblock-link.shown{font-weight:700}.swagger-ui span.token-string{color:#555}.swagger-ui span.token-not-formatted{color:#555;font-weight:700}.swagger-ui .btn{background:transparent;border:2px solid grey;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.1);color:#3b4151;font-family:sans-serif;font-size:14px;font-weight:700;padding:5px 23px;transition:all .3s}.swagger-ui .btn.btn-sm{font-size:12px;padding:4px 23px}.swagger-ui .btn[disabled]{cursor:not-allowed;opacity:.3}.swagger-ui .btn:hover{box-shadow:0 0 5px rgba(0,0,0,.3)}.swagger-ui .btn.cancel{background-color:transparent;border-color:#ff6060;color:#ff6060;font-family:sans-serif}.swagger-ui .btn.authorize{background-color:transparent;border-color:#49cc90;color:#49cc90;display:inline;line-height:1}.swagger-ui .btn.authorize span{float:left;padding:4px 20px 0 0}.swagger-ui .btn.authorize svg{fill:#49cc90}.swagger-ui .btn.execute{background-color:#4990e2;border-color:#4990e2;color:#fff}.swagger-ui .btn-group{display:flex;padding:30px}.swagger-ui .btn-group .btn{flex:1}.swagger-ui .btn-group .btn:first-child{border-radius:4px 0 0 4px}.swagger-ui .btn-group .btn:last-child{border-radius:0 4px 4px 0}.swagger-ui .authorization__btn{background:none;border:none;padding:0 0 0 10px}.swagger-ui .authorization__btn .locked{opacity:1}.swagger-ui .authorization__btn .unlocked{opacity:.4}.swagger-ui .model-box-control,.swagger-ui .models-control,.swagger-ui .opblock-summary-control{all:inherit;border-bottom:0;cursor:pointer;flex:1;padding:0}.swagger-ui .model-box-control:focus,.swagger-ui .models-control:focus,.swagger-ui .opblock-summary-control:focus{outline:auto}.swagger-ui .expand-methods,.swagger-ui .expand-operation{background:none;border:none}.swagger-ui .expand-methods svg,.swagger-ui .expand-operation svg{height:20px;width:20px}.swagger-ui .expand-methods{padding:0 10px}.swagger-ui .expand-methods:hover svg{fill:#404040}.swagger-ui .expand-methods svg{transition:all .3s;fill:#707070}.swagger-ui button{cursor:pointer}.swagger-ui button.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui .copy-to-clipboard{align-items:center;background:#7d8293;border:none;border-radius:4px;bottom:10px;display:flex;height:30px;justify-content:center;position:absolute;right:100px;width:30px}.swagger-ui .copy-to-clipboard button{background:url("data:image/svg+xml;charset=utf-8,") 50% no-repeat;border:none;flex-grow:1;flex-shrink:1;height:25px}.swagger-ui .copy-to-clipboard:active{background:#5e626f}.swagger-ui .opblock-control-arrow{background:none;border:none;text-align:center}.swagger-ui .curl-command .copy-to-clipboard{bottom:5px;height:20px;right:10px;width:20px}.swagger-ui .curl-command .copy-to-clipboard button{height:18px}.swagger-ui .opblock .opblock-summary .view-line-link.copy-to-clipboard{height:26px;position:static}.swagger-ui select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#f7f7f7 url("data:image/svg+xml;charset=utf-8,") right 10px center no-repeat;background-size:20px;border:2px solid #41444e;border-radius:4px;box-shadow:0 1px 2px 0 rgba(0,0,0,.25);color:#3b4151;font-family:sans-serif;font-size:14px;font-weight:700;padding:5px 40px 5px 10px}.swagger-ui select[multiple]{background:#f7f7f7;margin:5px 0;padding:5px}.swagger-ui select.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui .opblock-body select{min-width:230px}@media(max-width:768px){.swagger-ui .opblock-body select{min-width:180px}}@media(max-width:640px){.swagger-ui .opblock-body select{min-width:100%;width:100%}}.swagger-ui label{color:#3b4151;font-family:sans-serif;font-size:12px;font-weight:700;margin:0 0 5px}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{line-height:1}@media(max-width:768px){.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text]{max-width:175px}}.swagger-ui input[type=email],.swagger-ui input[type=file],.swagger-ui input[type=password],.swagger-ui input[type=search],.swagger-ui input[type=text],.swagger-ui textarea{background:#fff;border:1px solid #d9d9d9;border-radius:4px;margin:5px 0;min-width:100px;padding:8px 10px}.swagger-ui input[type=email].invalid,.swagger-ui input[type=file].invalid,.swagger-ui input[type=password].invalid,.swagger-ui input[type=search].invalid,.swagger-ui input[type=text].invalid,.swagger-ui textarea.invalid{animation:shake .4s 1;background:#feebeb;border-color:#f93e3e}.swagger-ui input[disabled],.swagger-ui select[disabled],.swagger-ui textarea[disabled]{background-color:#fafafa;color:#888;cursor:not-allowed}.swagger-ui select[disabled]{border-color:#888}.swagger-ui textarea[disabled]{background-color:#41444e;color:#fff}@keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}.swagger-ui textarea{background:hsla(0,0%,100%,.8);border:none;border-radius:4px;color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;min-height:280px;outline:none;padding:10px;width:100%}.swagger-ui textarea:focus{border:2px solid #61affe}.swagger-ui textarea.curl{background:#41444e;border-radius:4px;color:#fff;font-family:monospace;font-size:12px;font-weight:600;margin:0;min-height:100px;padding:10px;resize:none}.swagger-ui .checkbox{color:#303030;padding:5px 0 10px;transition:opacity .5s}.swagger-ui .checkbox label{display:flex}.swagger-ui .checkbox p{color:#3b4151;font-family:monospace;font-style:italic;font-weight:400!important;font-weight:600;margin:0!important}.swagger-ui .checkbox input[type=checkbox]{display:none}.swagger-ui .checkbox input[type=checkbox]+label>.item{background:#e8e8e8;border-radius:1px;box-shadow:0 0 0 2px #e8e8e8;cursor:pointer;display:inline-block;flex:none;height:16px;margin:0 8px 0 0;padding:5px;position:relative;top:3px;width:16px}.swagger-ui .checkbox input[type=checkbox]+label>.item:active{transform:scale(.9)}.swagger-ui .checkbox input[type=checkbox]:checked+label>.item{background:#e8e8e8 url("data:image/svg+xml;charset=utf-8,") 50% no-repeat}.swagger-ui .dialog-ux{bottom:0;left:0;position:fixed;right:0;top:0;z-index:9999}.swagger-ui .dialog-ux .backdrop-ux{background:rgba(0,0,0,.8);bottom:0;left:0;position:fixed;right:0;top:0}.swagger-ui .dialog-ux .modal-ux{background:#fff;border:1px solid #ebebeb;border-radius:4px;box-shadow:0 10px 30px 0 rgba(0,0,0,.2);left:50%;max-width:650px;min-width:300px;position:absolute;top:50%;transform:translate(-50%,-50%);width:100%;z-index:9999}.swagger-ui .dialog-ux .modal-ux-content{max-height:540px;overflow-y:auto;padding:20px}.swagger-ui .dialog-ux .modal-ux-content p{color:#41444e;color:#3b4151;font-family:sans-serif;font-size:12px;margin:0 0 5px}.swagger-ui .dialog-ux .modal-ux-content h4{color:#3b4151;font-family:sans-serif;font-size:18px;font-weight:600;margin:15px 0 0}.swagger-ui .dialog-ux .modal-ux-header{align-items:center;border-bottom:1px solid #ebebeb;display:flex;padding:12px 0}.swagger-ui .dialog-ux .modal-ux-header .close-modal{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:none;padding:0 10px}.swagger-ui .dialog-ux .modal-ux-header h3{color:#3b4151;flex:1;font-family:sans-serif;font-size:20px;font-weight:600;margin:0;padding:0 20px}.swagger-ui .model{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300;font-weight:600}.swagger-ui .model .deprecated span,.swagger-ui .model .deprecated td{color:#a0a0a0!important}.swagger-ui .model .deprecated>td:first-of-type{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .model-toggle{cursor:pointer;display:inline-block;font-size:10px;margin:auto .3em;position:relative;top:6px;transform:rotate(90deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .model-toggle.collapsed{transform:rotate(0deg)}.swagger-ui .model-toggle:after{background:url("data:image/svg+xml;charset=utf-8,") 50% no-repeat;background-size:100%;content:"";display:block;height:20px;width:20px}.swagger-ui .model-jump-to-path{cursor:pointer;position:relative}.swagger-ui .model-jump-to-path .view-line-link{cursor:pointer;position:absolute;top:-.4em}.swagger-ui .model-title{position:relative}.swagger-ui .model-title:hover .model-hint{display:block}.swagger-ui .model-hint{background:rgba(0,0,0,.7);border-radius:4px;color:#ebebeb;display:none;padding:.1em .5em;position:absolute;top:-1.8em;white-space:nowrap}.swagger-ui .model p{margin:0 0 1em}.swagger-ui .model .property{color:#999;font-style:italic}.swagger-ui .model .property.primitive{color:#6b6b6b}.swagger-ui .model .property.primitive.extension{display:block}.swagger-ui .model .property.primitive.extension>td:first-child{padding-left:0;padding-right:0;width:auto}.swagger-ui .model .property.primitive.extension>td:first-child:after{content:": "}.swagger-ui .model .external-docs,.swagger-ui table.model tr.description{color:#666;font-weight:400}.swagger-ui table.model tr.description td:first-child,.swagger-ui table.model tr.property-row.required td:first-child{font-weight:700}.swagger-ui table.model tr.property-row td{vertical-align:top}.swagger-ui table.model tr.property-row td:first-child{padding-right:.2em}.swagger-ui table.model tr.property-row .star{color:red}.swagger-ui table.model tr.extension{color:#777}.swagger-ui table.model tr.extension td:last-child{vertical-align:top}.swagger-ui table.model tr.external-docs td:first-child{font-weight:700}.swagger-ui table.model tr .renderedMarkdown p:first-child{margin-top:0}.swagger-ui section.models{border:1px solid rgba(59,65,81,.3);border-radius:4px;margin:30px 0}.swagger-ui section.models .pointer{cursor:pointer}.swagger-ui section.models.is-open{padding:0 0 20px}.swagger-ui section.models.is-open h4{border-bottom:1px solid rgba(59,65,81,.3);margin:0 0 5px}.swagger-ui section.models h4{align-items:center;color:#606060;cursor:pointer;display:flex;font-family:sans-serif;font-size:16px;margin:0;padding:10px 20px 10px 10px;transition:all .2s}.swagger-ui section.models h4 svg{transition:all .4s}.swagger-ui section.models h4 span{flex:1}.swagger-ui section.models h4:hover{background:rgba(0,0,0,.02)}.swagger-ui section.models h5{color:#707070;font-family:sans-serif;font-size:16px;margin:0 0 10px}.swagger-ui section.models .model-jump-to-path{position:relative;top:5px}.swagger-ui section.models .model-container{background:rgba(0,0,0,.05);border-radius:4px;margin:0 20px 15px;position:relative;transition:all .5s}.swagger-ui section.models .model-container:hover{background:rgba(0,0,0,.07)}.swagger-ui section.models .model-container:first-of-type{margin:20px}.swagger-ui section.models .model-container:last-of-type{margin:0 20px}.swagger-ui section.models .model-container .models-jump-to-path{opacity:.65;position:absolute;right:5px;top:8px}.swagger-ui section.models .model-box{background:none}.swagger-ui section.models .model-box:has(.model-box){overflow-x:auto;width:100%}.swagger-ui .model-box{background:rgba(0,0,0,.1);border-radius:4px;display:inline-block;padding:10px}.swagger-ui .model-box .model-jump-to-path{position:relative;top:4px}.swagger-ui .model-box.deprecated{opacity:.5}.swagger-ui .model-title{color:#505050;font-family:sans-serif;font-size:16px}.swagger-ui .model-title img{bottom:0;margin-left:1em;position:relative}.swagger-ui .model-deprecated-warning{color:#f93e3e;font-family:sans-serif;font-size:16px;font-weight:600;margin-right:1em}.swagger-ui span>span.model .brace-close{padding:0 0 0 10px}.swagger-ui .prop-name{display:inline-block;margin-right:1em}.swagger-ui .prop-type{color:#55a}.swagger-ui .prop-enum{display:block}.swagger-ui .prop-format{color:#606060}.swagger-ui .servers>label{color:#3b4151;font-family:sans-serif;font-size:12px;margin:-20px 15px 0 0}.swagger-ui .servers>label select{max-width:100%;min-width:130px;width:100%}.swagger-ui .servers h4.message{padding-bottom:2em}.swagger-ui .servers table tr{width:30em}.swagger-ui .servers table td{display:inline-block;max-width:15em;padding-bottom:10px;padding-top:10px;vertical-align:middle}.swagger-ui .servers table td:first-of-type{padding-right:1em}.swagger-ui .servers table td input{height:100%;width:100%}.swagger-ui .servers .computed-url{margin:2em 0}.swagger-ui .servers .computed-url code{display:inline-block;font-size:16px;margin:0 1em;padding:4px}.swagger-ui .servers-title{font-size:12px;font-weight:700}.swagger-ui .operation-servers h4.message{margin-bottom:2em}.swagger-ui table{border-collapse:collapse;padding:0 10px;width:100%}.swagger-ui table.model tbody tr td{padding:0;vertical-align:top}.swagger-ui table.model tbody tr td:first-of-type{padding:0 0 0 2em;width:174px}.swagger-ui table.headers td{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300;font-weight:600;vertical-align:middle}.swagger-ui table.headers .header-example{color:#999;font-style:italic}.swagger-ui table tbody tr td{padding:10px 0 0;vertical-align:top}.swagger-ui table tbody tr td:first-of-type{min-width:6em;padding:10px 0}.swagger-ui table tbody tr td:has(.model-box){max-width:1px}.swagger-ui table thead tr td,.swagger-ui table thead tr th{border-bottom:1px solid rgba(59,65,81,.2);color:#3b4151;font-family:sans-serif;font-size:12px;font-weight:700;padding:12px 0;text-align:left}.swagger-ui .parameters-col_description{margin-bottom:2em;width:99%}.swagger-ui .parameters-col_description input{max-width:340px;width:100%}.swagger-ui .parameters-col_description select{border-width:1px}.swagger-ui .parameters-col_description .markdown p,.swagger-ui .parameters-col_description .renderedMarkdown p{margin:0}.swagger-ui .parameter__name{color:#3b4151;font-family:sans-serif;font-size:16px;font-weight:400;margin-right:.75em}.swagger-ui .parameter__name.required{font-weight:700}.swagger-ui .parameter__name.required span{color:red}.swagger-ui .parameter__name.required:after{color:rgba(255,0,0,.6);content:"required";font-size:10px;padding:5px;position:relative;top:-6px}.swagger-ui .parameter__extension,.swagger-ui .parameter__in{color:grey;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .parameter__deprecated{color:red;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .parameter__empty_value_toggle{display:block;font-size:13px;padding-bottom:12px;padding-top:5px}.swagger-ui .parameter__empty_value_toggle input{margin-right:7px;width:auto}.swagger-ui .parameter__empty_value_toggle.disabled{opacity:.7}.swagger-ui .table-container{padding:20px}.swagger-ui .response-col_description{width:99%}.swagger-ui .response-col_description .markdown p,.swagger-ui .response-col_description .renderedMarkdown p{margin:0}.swagger-ui .response-col_links{min-width:6em}.swagger-ui .response__extension{color:grey;font-family:monospace;font-size:12px;font-style:italic;font-weight:600}.swagger-ui .topbar{background-color:#1b1b1b;padding:10px 0}.swagger-ui .topbar .topbar-wrapper{align-items:center;display:flex;flex-wrap:wrap;gap:10px}@media(max-width:550px){.swagger-ui .topbar .topbar-wrapper{align-items:start;flex-direction:column}}.swagger-ui .topbar a{align-items:center;color:#fff;display:flex;flex:1;font-family:sans-serif;font-size:1.5em;font-weight:700;max-width:300px;-webkit-text-decoration:none;text-decoration:none}.swagger-ui .topbar a span{margin:0;padding:0 10px}.swagger-ui .topbar .download-url-wrapper{display:flex;flex:3;justify-content:flex-end}.swagger-ui .topbar .download-url-wrapper input[type=text]{border:2px solid #62a03f;border-radius:4px 0 0 4px;margin:0;max-width:100%;outline:none;width:100%}.swagger-ui .topbar .download-url-wrapper .select-label{align-items:center;color:#f0f0f0;display:flex;margin:0;max-width:600px;width:100%}.swagger-ui .topbar .download-url-wrapper .select-label span{flex:1;font-size:16px;padding:0 10px 0 0;text-align:right}.swagger-ui .topbar .download-url-wrapper .select-label select{border:2px solid #62a03f;box-shadow:none;flex:2;outline:none;width:100%}.swagger-ui .topbar .download-url-wrapper .download-url-button{background:#62a03f;border:none;border-radius:0 4px 4px 0;color:#fff;font-family:sans-serif;font-size:16px;font-weight:700;padding:4px 30px}@media(max-width:550px){.swagger-ui .topbar .download-url-wrapper{width:100%}}.swagger-ui .info{margin:50px 0}.swagger-ui .info.failed-config{margin-left:auto;margin-right:auto;max-width:880px;text-align:center}.swagger-ui .info hgroup.main{margin:0 0 20px}.swagger-ui .info hgroup.main a{font-size:12px}.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info pre,.swagger-ui .info table{font-size:14px}.swagger-ui .info h1,.swagger-ui .info h2,.swagger-ui .info h3,.swagger-ui .info h4,.swagger-ui .info h5,.swagger-ui .info li,.swagger-ui .info p,.swagger-ui .info table{color:#3b4151;font-family:sans-serif}.swagger-ui .info a{color:#4990e2;font-family:sans-serif;font-size:14px;transition:all .4s}.swagger-ui .info a:hover{color:#1f69c0}.swagger-ui .info>div{margin:0 0 5px}.swagger-ui .info .base-url{color:#3b4151;font-family:monospace;font-size:12px;font-weight:300!important;font-weight:600;margin:0}.swagger-ui .info .title{color:#3b4151;font-family:sans-serif;font-size:36px;margin:0}.swagger-ui .info .title small{background:#7d8492;border-radius:57px;display:inline-block;font-size:10px;margin:0 0 0 5px;padding:2px 4px;position:relative;top:-5px;vertical-align:super}.swagger-ui .info .title small.version-stamp{background-color:#89bf04}.swagger-ui .info .title small pre{color:#fff;font-family:sans-serif;margin:0;padding:0}.swagger-ui .auth-btn-wrapper{display:flex;justify-content:center;padding:10px 0}.swagger-ui .auth-btn-wrapper .btn-done{margin-right:1em}.swagger-ui .auth-wrapper{display:flex;flex:1;justify-content:flex-end}.swagger-ui .auth-wrapper .authorize{margin-left:10px;margin-right:10px;padding-right:20px}.swagger-ui .auth-container{border-bottom:1px solid #ebebeb;margin:0 0 10px;padding:10px 20px}.swagger-ui .auth-container:last-of-type{border:0;margin:0;padding:10px 20px}.swagger-ui .auth-container h4{margin:5px 0 15px!important}.swagger-ui .auth-container .wrapper{margin:0;padding:0}.swagger-ui .auth-container input[type=password],.swagger-ui .auth-container input[type=text]{min-width:230px}.swagger-ui .auth-container .errors{background-color:#fee;border-radius:4px;color:red;color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;margin:1em;padding:10px}.swagger-ui .auth-container .errors b{margin-right:1em;text-transform:capitalize}.swagger-ui .scopes h2{color:#3b4151;font-family:sans-serif;font-size:14px}.swagger-ui .scopes h2 a{color:#4990e2;cursor:pointer;font-size:12px;padding-left:10px;-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .scope-def{padding:0 0 20px}.swagger-ui .errors-wrapper{animation:scaleUp .5s;background:rgba(249,62,62,.1);border:2px solid #f93e3e;border-radius:4px;margin:20px;padding:10px 20px}.swagger-ui .errors-wrapper .error-wrapper{margin:0 0 10px}.swagger-ui .errors-wrapper .errors h4{color:#3b4151;font-family:monospace;font-size:14px;font-weight:600;margin:0}.swagger-ui .errors-wrapper .errors small{color:#606060}.swagger-ui .errors-wrapper .errors .message{white-space:pre-line}.swagger-ui .errors-wrapper .errors .message.thrown{max-width:100%}.swagger-ui .errors-wrapper .errors .error-line{cursor:pointer;-webkit-text-decoration:underline;text-decoration:underline}.swagger-ui .errors-wrapper hgroup{align-items:center;display:flex}.swagger-ui .errors-wrapper hgroup h4{color:#3b4151;flex:1;font-family:sans-serif;font-size:20px;margin:0}@keyframes scaleUp{0%{opacity:0;transform:scale(.8)}to{opacity:1;transform:scale(1)}}.swagger-ui .Resizer.vertical.disabled{display:none}.swagger-ui .markdown p,.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown p,.swagger-ui .renderedMarkdown pre{margin:1em auto;word-break:break-all;word-break:break-word}.swagger-ui .markdown pre,.swagger-ui .renderedMarkdown pre{background:none;color:#000;font-weight:400;padding:0;white-space:pre-wrap}.swagger-ui .markdown code,.swagger-ui .renderedMarkdown code{background:rgba(0,0,0,.05);border-radius:4px;color:#9012fe;font-family:monospace;font-size:14px;font-weight:600;padding:5px 7px}.swagger-ui .markdown pre>code,.swagger-ui .renderedMarkdown pre>code{display:block}.swagger-ui .json-schema-2020-12-keyword--\$vocabulary ul{border-left:1px dashed rgba(0,0,0,.1);margin:0 0 0 20px}.swagger-ui .json-schema-2020-12-\$vocabulary-uri{margin-left:35px}.swagger-ui .json-schema-2020-12-\$vocabulary-uri--disabled{-webkit-text-decoration:line-through;text-decoration:line-through}.swagger-ui .json-schema-2020-12-keyword--const .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-keyword--const .json-schema-2020-12-json-viewer__value{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12__constraint{background-color:#805ad5;border-radius:4px;color:#3b4151;color:#fff;font-family:monospace;font-weight:600;line-height:1.5;margin-left:10px;padding:1px 3px}.swagger-ui .json-schema-2020-12__constraint--string{background-color:#d69e2e;color:#fff}.swagger-ui .json-schema-2020-12-keyword--default .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-keyword--default .json-schema-2020-12-json-viewer__value{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-keyword--dependentRequired>ul{display:inline-block;margin:0;padding:0}.swagger-ui .json-schema-2020-12-keyword--dependentRequired>ul li{display:inline;list-style-type:none}.swagger-ui .json-schema-2020-12-keyword--description{color:#6b6b6b;font-size:12px;margin-left:20px}.swagger-ui .json-schema-2020-12-keyword--description p{margin:0}.swagger-ui .json-schema-2020-12-keyword--enum .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-keyword--enum .json-schema-2020-12-json-viewer__value,.swagger-ui .json-schema-2020-12-keyword--examples .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-keyword--examples .json-schema-2020-12-json-viewer__value{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-json-viewer-extension-keyword .json-schema-2020-12-json-viewer__name,.swagger-ui .json-schema-2020-12-json-viewer-extension-keyword .json-schema-2020-12-json-viewer__value{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-keyword--patternProperties ul{border:none;margin:0;padding:0}.swagger-ui .json-schema-2020-12-keyword--patternProperties .json-schema-2020-12__title:first-of-type:after,.swagger-ui .json-schema-2020-12-keyword--patternProperties .json-schema-2020-12__title:first-of-type:before{color:#55a;content:"/"}.swagger-ui .json-schema-2020-12-keyword--properties>ul{border:none;margin:0;padding:0}.swagger-ui .json-schema-2020-12-property{list-style-type:none}.swagger-ui .json-schema-2020-12-property--required>.json-schema-2020-12:first-of-type>.json-schema-2020-12-head .json-schema-2020-12__title:after{color:red;content:"*";font-weight:700}.swagger-ui .json-schema-2020-12__title{color:#505050;display:inline-block;font-family:sans-serif;font-size:12px;font-weight:700;line-height:normal}.swagger-ui .json-schema-2020-12__title .json-schema-2020-12-keyword__name{margin:0}.swagger-ui .json-schema-2020-12-property{margin:7px 0}.swagger-ui .json-schema-2020-12-property .json-schema-2020-12__title{color:#3b4151;font-family:monospace;font-size:12px;font-weight:600;vertical-align:middle}.swagger-ui .json-schema-2020-12-keyword{margin:5px 0}.swagger-ui .json-schema-2020-12-keyword__children{border-left:1px dashed rgba(0,0,0,.1);margin:0 0 0 20px;padding:0}.swagger-ui .json-schema-2020-12-keyword__children--collapsed{display:none}.swagger-ui .json-schema-2020-12-keyword__name{font-size:12px;font-weight:700;margin-left:20px}.swagger-ui .json-schema-2020-12-keyword__name--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-keyword__name--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__name--extension{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__value{color:#6b6b6b;font-size:12px;font-style:italic;font-weight:400}.swagger-ui .json-schema-2020-12-keyword__value--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-keyword__value--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__value--extension{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-keyword__value--warning{border:1px dashed red;border-radius:4px;color:#3b4151;color:red;display:inline-block;font-family:monospace;font-style:normal;font-weight:600;line-height:1.5;margin-left:10px;padding:1px 4px}.swagger-ui .json-schema-2020-12-keyword__name--secondary+.json-schema-2020-12-keyword__value--secondary:before{content:"="}.swagger-ui .json-schema-2020-12__attribute{color:#3b4151;font-family:monospace;font-size:12px;padding-left:10px;text-transform:lowercase}.swagger-ui .json-schema-2020-12__attribute--primary{color:#55a}.swagger-ui .json-schema-2020-12__attribute--muted{color:gray}.swagger-ui .json-schema-2020-12__attribute--warning{color:red}.swagger-ui .json-schema-2020-12-json-viewer{margin:5px 0}.swagger-ui .json-schema-2020-12-json-viewer__children{border-left:1px dashed rgba(0,0,0,.1);margin:0 0 0 20px;padding:0}.swagger-ui .json-schema-2020-12-json-viewer__children--collapsed{display:none}.swagger-ui .json-schema-2020-12-json-viewer__name{font-size:12px;font-weight:700;margin-left:20px}.swagger-ui .json-schema-2020-12-json-viewer__name--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-json-viewer__name--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-json-viewer__name--extension{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-json-viewer__value{color:#6b6b6b;font-size:12px;font-style:italic;font-weight:400}.swagger-ui .json-schema-2020-12-json-viewer__value--primary{color:#3b4151;font-style:normal}.swagger-ui .json-schema-2020-12-json-viewer__value--secondary{color:#6b6b6b;font-style:italic}.swagger-ui .json-schema-2020-12-json-viewer__value--extension{color:#929292;font-style:italic}.swagger-ui .json-schema-2020-12-json-viewer__value--warning{border:1px dashed red;border-radius:4px;color:#3b4151;color:red;display:inline-block;font-family:monospace;font-style:normal;font-weight:600;line-height:1.5;margin-left:10px;padding:1px 4px}.swagger-ui .json-schema-2020-12-json-viewer__name--secondary+.json-schema-2020-12-json-viewer__value--secondary:before{content:"="}.swagger-ui .json-schema-2020-12{background-color:rgba(0,0,0,.05);border-radius:4px;margin:0 20px 15px;padding:12px 0 12px 20px}.swagger-ui .json-schema-2020-12:first-of-type{margin:20px}.swagger-ui .json-schema-2020-12:last-of-type{margin:0 20px}.swagger-ui .json-schema-2020-12--embedded{background-color:inherit;padding-bottom:0;padding-left:inherit;padding-right:inherit;padding-top:0}.swagger-ui .json-schema-2020-12-body{border-left:1px dashed rgba(0,0,0,.1);margin:2px 0}.swagger-ui .json-schema-2020-12-body--collapsed{display:none}.swagger-ui .json-schema-2020-12-accordion{border:none;outline:none;padding-left:0}.swagger-ui .json-schema-2020-12-accordion__children{display:inline-block}.swagger-ui .json-schema-2020-12-accordion__icon{display:inline-block;height:18px;vertical-align:bottom;width:18px}.swagger-ui .json-schema-2020-12-accordion__icon--expanded{transform:rotate(-90deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .json-schema-2020-12-accordion__icon--collapsed{transform:rotate(0deg);transform-origin:50% 50%;transition:transform .15s ease-in}.swagger-ui .json-schema-2020-12-accordion__icon svg{height:20px;width:20px}.swagger-ui .json-schema-2020-12-expand-deep-button{border:none;color:#505050;color:#afaeae;font-family:sans-serif;font-size:12px;padding-right:0}.swagger-ui .model-box .json-schema-2020-12:not(.json-schema-2020-12--embedded)>.json-schema-2020-12-head .json-schema-2020-12__title:first-of-type{font-size:16px}.swagger-ui .model-box>.json-schema-2020-12{margin:0}.swagger-ui .model-box .json-schema-2020-12{background-color:transparent;padding:0}.swagger-ui .model-box .json-schema-2020-12-accordion,.swagger-ui .model-box .json-schema-2020-12-expand-deep-button{background-color:transparent}.swagger-ui .models .json-schema-2020-12:not(.json-schema-2020-12--embedded)>.json-schema-2020-12-head .json-schema-2020-12__title:first-of-type{font-size:16px}.swagger-ui .models .json-schema-2020-12:not(.json-schema-2020-12--embedded){overflow-x:auto;width:calc(100% - 40px)} \ No newline at end of file diff --git a/sandbox/package-lock.json b/sandbox/package-lock.json index 2006b1d..635815f 100644 --- a/sandbox/package-lock.json +++ b/sandbox/package-lock.json @@ -1,12 +1,12 @@ { "name": "sandbox", - "version": "1.0.0", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sandbox", - "version": "1.0.0", + "version": "1.0.1", "license": "ISC", "dependencies": { "chart.js": "^4.5.1", @@ -30,64 +30,64 @@ } }, "..": { - "version": "2.2.1", + "version": "2.4.1", "license": "MIT", "dependencies": { "@fastify/busboy": "^3.2.0", - "cors": "^2.8.5", - "express": "^5.1.0", + "cors": "^2.8.6", + "express": "^5.2.1", "glob": "^13.0.6", "node-cron": "^4.2.1", - "nodemailer": "^7.0.6", - "pg": "^8.16.3", + "nodemailer": "^8.0.8", + "pg": "^8.21.0", "prom-client": "^15.1.3", "reflect-metadata": "^0.2.2", - "swagger-ui-express": "^5.0.1", "swagger2openapi": "^7.0.8", - "systeminformation": "^5.27.10", - "typeorm": "^0.3.27" + "systeminformation": "^5.31.6", + "typeorm": "^0.3.29" }, "devDependencies": { "@faker-js/faker": "^9.9.0", - "@jest/globals": "^30.2.0", - "@swc/core": "^1.13.5", - "@testcontainers/postgresql": "^11.6.0", + "@jest/globals": "^30.4.1", + "@swc/core": "^1.15.40", + "@testcontainers/postgresql": "^12.0.0", + "@types/cookie-parser": "^1.4.10", "@types/cors": "^2.8.19", - "@types/express": "^5.0.3", + "@types/express": "^5.0.6", "@types/jsonwebtoken": "^9.0.10", - "@types/node": "^24.10.13", - "@types/nodemailer": "^7.0.1", + "@types/node": "^25.9.1", + "@types/nodemailer": "^8.0.0", "@types/pdfmake": "^0.2.11", - "@types/swagger-ui-express": "^4.1.8", "@types/swagger2openapi": "^7.0.4", "eslint": "^9.36.0", - "eslint-config-service-soft": "^2.1.4", - "jest": "^30.2.0", + "eslint-config-service-soft": "^2.1.6", + "jest": "^30.4.2", "npm-run-all": "^4.1.5", "openapi3-ts": "^4.5.0", - "testcontainers": "^11.6.0", - "ts-jest": "^29.4.6", - "typedoc": "^0.28.17", - "typescript": "^5.9.3" + "socket.io-client": "^4.8.3", + "testcontainers": "^12.0.0", + "ts-jest": "^29.4.11", + "typedoc": "^0.28.19", + "typescript": "^5.9.2" }, "engines": { "node": ">=20" }, "peerDependencies": { - "axios": "^1.13.2", - "bcryptjs": "^3.0.2", - "bignumber.js": "^9.3.1", - "handlebars": "^4.7.8", + "axios": "^1.16.1", + "bcryptjs": "^3.0.3", + "bignumber.js": "^11.1.1", + "cookie-parser": "^1.4.7", + "handlebars": "^4.7.9", "hi-base32": "^0.5.1", - "jsonwebtoken": "^9.0.2", - "otpauth": "^9.4.1", - "pdfmake": "^0.2.2", - "preact": "^10.28.3", - "preact-render-to-string": "^6.6.5", + "jsonwebtoken": "^9.0.3", + "otpauth": "^9.5.1", + "pdfmake": "^0.2.23", + "preact": "^10.29.2", + "preact-render-to-string": "^6.7.0", "rxjs": "^7.8.2", - "socket.io": "^4.8.1", + "socket.io": "^4.8.3", "ts-node": "^10.9.2", - "uuid": "^11.1.0", "xmlbuilder2": "^4.0.3" } }, @@ -963,16 +963,6 @@ "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/serialize-javascript": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.4.tgz", - "integrity": "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/cosmiconfig": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", @@ -1113,15 +1103,15 @@ } }, "node_modules/engine.io-client": { - "version": "6.6.4", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", - "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.5.tgz", + "integrity": "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", - "ws": "~8.18.3", + "ws": "~8.20.1", "xmlhttprequest-ssl": "~2.1.1" } }, @@ -1293,9 +1283,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -1440,9 +1430,9 @@ "license": "ISC" }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "peer": true, @@ -1937,9 +1927,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", "dev": true, "funding": [ { @@ -2105,9 +2095,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -2151,9 +2141,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -2172,7 +2162,7 @@ "license": "MIT", "peer": true, "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -2619,6 +2609,16 @@ "node": ">=10" } }, + "node_modules/serialize-javascript": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -2671,9 +2671,9 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", - "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", + "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -2979,9 +2979,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "peer": true, @@ -3291,9 +3291,9 @@ "license": "MIT" }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { "node": ">=10.0.0" diff --git a/sandbox/package.json b/sandbox/package.json index 99b62c9..34ee806 100644 --- a/sandbox/package.json +++ b/sandbox/package.json @@ -1,6 +1,6 @@ { "name": "sandbox", - "version": "1.0.0", + "version": "1.0.1", "main": "./dist/index.js", "scripts": { "start": "webpack --watch", diff --git a/sandbox/src/controllers/cron.controller.ts b/sandbox/src/controllers/cron.controller.ts index e80410a..f634817 100644 --- a/sandbox/src/controllers/cron.controller.ts +++ b/sandbox/src/controllers/cron.controller.ts @@ -1,6 +1,6 @@ import { Controller, Post, Response, Inject, ZIBRI_DI_TOKENS, CronService, CronJobEntity, Get, InjectRepository, Repository } from 'zibri'; -@Controller('/cron') +@Controller('/cron', { versions: 'all' }) export class CronController { constructor( @Inject(ZIBRI_DI_TOKENS.CRON_SERVICE) diff --git a/sandbox/src/controllers/file.controller.ts b/sandbox/src/controllers/file.controller.ts index 110a57e..5b99ca0 100644 --- a/sandbox/src/controllers/file.controller.ts +++ b/sandbox/src/controllers/file.controller.ts @@ -5,7 +5,7 @@ export class FileCreateDTO { file!: File; } -@Controller('/files') +@Controller('/files', { versions: 'all' }) export class FileController { constructor( @Inject(ZIBRI_DI_TOKENS.ASSET_SERVICE) diff --git a/sandbox/src/controllers/metrics.controller.ts b/sandbox/src/controllers/metrics.controller.ts index 12d0734..cebc40e 100644 --- a/sandbox/src/controllers/metrics.controller.ts +++ b/sandbox/src/controllers/metrics.controller.ts @@ -3,7 +3,7 @@ import { Controller, Inject, ZIBRI_DI_TOKENS, Metric, Get, Response, MetricsSnap import { StaticPagesCache } from './page.controller'; import { MetricsPage } from '../templates/pages/metrics'; -@Controller('/metrics') +@Controller('/metrics', { versions: 'all' }) export class MetricsController { constructor( @Inject(ZIBRI_DI_TOKENS.METRICS_SERVICE) diff --git a/sandbox/src/controllers/page.controller.ts b/sandbox/src/controllers/page.controller.ts index b9164e1..d0b94b3 100644 --- a/sandbox/src/controllers/page.controller.ts +++ b/sandbox/src/controllers/page.controller.ts @@ -17,7 +17,7 @@ export class StaticPagesCache extends WriteThroughReadThroughCache 'index') diff --git a/sandbox/src/controllers/template.controller.ts b/sandbox/src/controllers/template.controller.ts index 5678fc5..e50c92d 100644 --- a/sandbox/src/controllers/template.controller.ts +++ b/sandbox/src/controllers/template.controller.ts @@ -4,7 +4,7 @@ import { LogEmail } from '../templates/emails/log'; import { PasswordResetEmail } from '../templates/emails/password-reset'; import { SocketIoTestPage } from '../templates/pages/socket-io-test'; -@Controller('/templates') +@Controller('/templates', { versions: 'all' }) export class TemplateController { @Response.html() diff --git a/sandbox/src/controllers/test-crud.controller.ts b/sandbox/src/controllers/test-crud.controller.ts index 22688fb..8c35469 100644 --- a/sandbox/src/controllers/test-crud.controller.ts +++ b/sandbox/src/controllers/test-crud.controller.ts @@ -3,7 +3,7 @@ import { Controller, CrudController, IntersectionClass, OmitClass, PickClass } f import { Test, TestCreateDTO, TestUpdateDTO } from '../models'; import { TemplateController } from './template.controller'; -@Controller('/tests-crud') +@Controller('/tests-crud', { versions: 'all' }) export class TestCrudController extends IntersectionClass( OmitClass(CrudController(Test, TestCreateDTO, TestUpdateDTO), ['deleteById']), PickClass(TemplateController, ['getMailTemplate']) diff --git a/sandbox/src/controllers/test.controller.ts b/sandbox/src/controllers/test.controller.ts index 0576e27..48d749a 100644 --- a/sandbox/src/controllers/test.controller.ts +++ b/sandbox/src/controllers/test.controller.ts @@ -4,7 +4,7 @@ import { Roles, Test, TestCreateDTO, User } from '../models'; import { UserRepository } from '../repositories'; @Auth.isLoggedIn() -@Controller('/tests') +@Controller('/tests', { versions: 'all' }) export class TestController { constructor( @InjectRepository(Test) diff --git a/sandbox/src/controllers/test.websocket-controller.ts b/sandbox/src/controllers/test.websocket-controller.ts index 9013c4a..b006b6f 100644 --- a/sandbox/src/controllers/test.websocket-controller.ts +++ b/sandbox/src/controllers/test.websocket-controller.ts @@ -5,7 +5,7 @@ export class CreateWebsocketChatMessageDTO { message!: string; } -@WebsocketController() +@WebsocketController({ versions: 'all' }) export class TestWebsocketController { constructor( @Inject(ZIBRI_DI_TOKENS.WEBSOCKET_SERVICE) diff --git a/sandbox/src/index.ts b/sandbox/src/index.ts index 2041dc4..ff70f2d 100644 --- a/sandbox/src/index.ts +++ b/sandbox/src/index.ts @@ -1,5 +1,5 @@ import H from 'handlebars/runtime'; -import { inject, isVersion, JwtAuthController, LoggerInterface, MailingListController, ZIBRI_DI_TOKENS, ZibriApplication, ZibriInvoicingPlugin, ZibriMailingListPlugin } from 'zibri'; +import { inject, JwtAuthController, LoggerInterface, MailingListController, SemVerUtilities, ZIBRI_DI_TOKENS, ZibriApplication, ZibriInvoicingPlugin, ZibriMailingListPlugin } from 'zibri'; import { CronController, FileController, MetricsController, PageController, TemplateController, TestController, TestCrudController, TestWebsocketController } from './controllers'; import { createDefaultData } from './create-default-data.function'; @@ -11,7 +11,7 @@ import { providers } from './providers'; export let logger: LoggerInterface; async function start(): Promise { - if (!isVersion(version)) { + if (!SemVerUtilities.isSemVerVersion(version)) { throw new Error('The version of the package.json is not valid.'); } diff --git a/sandbox/src/models/generated/petstore/index.ts b/sandbox/src/models/generated/petstore/index.ts index 21f09fa..a14bef2 100644 --- a/sandbox/src/models/generated/petstore/index.ts +++ b/sandbox/src/models/generated/petstore/index.ts @@ -3,6 +3,4 @@ export * from './petstore.category.model'; export * from './petstore.pet.model'; export * from './petstore.tag.model'; export * from './petstore.order.model'; -export * from './petstore.user.model'; -export * from './petstore.upload-file.model'; -export * from './petstore.update-pet-with-form.model'; \ No newline at end of file +export * from './petstore.user.model'; \ No newline at end of file diff --git a/sandbox/src/models/generated/petstore/petstore.api-response.model.ts b/sandbox/src/models/generated/petstore/petstore.api-response.model.ts index 6d3c6c5..0d55094 100644 --- a/sandbox/src/models/generated/petstore/petstore.api-response.model.ts +++ b/sandbox/src/models/generated/petstore/petstore.api-response.model.ts @@ -3,11 +3,11 @@ import { Property } from 'zibri'; export class PetstoreApiResponse { @Property.number({ required: false }) - code?: number; + 'code'?: number; @Property.string({ required: false }) - type?: string; + 'type'?: string; @Property.string({ required: false }) - message?: string; + 'message'?: string; } \ No newline at end of file diff --git a/sandbox/src/models/generated/petstore/petstore.category.model.ts b/sandbox/src/models/generated/petstore/petstore.category.model.ts index 4b5b802..9bbe4ef 100644 --- a/sandbox/src/models/generated/petstore/petstore.category.model.ts +++ b/sandbox/src/models/generated/petstore/petstore.category.model.ts @@ -3,8 +3,8 @@ import { Property } from 'zibri'; export class PetstoreCategory { @Property.number({ required: false }) - id?: number; + 'id'?: number; @Property.string({ required: false }) - name?: string; + 'name'?: string; } \ No newline at end of file diff --git a/sandbox/src/models/generated/petstore/petstore.order.model.ts b/sandbox/src/models/generated/petstore/petstore.order.model.ts index e84a225..960a3cd 100644 --- a/sandbox/src/models/generated/petstore/petstore.order.model.ts +++ b/sandbox/src/models/generated/petstore/petstore.order.model.ts @@ -3,20 +3,20 @@ import { Property } from 'zibri'; export class PetstoreOrder { @Property.number({ required: false }) - id?: number; + 'id'?: number; @Property.number({ required: false }) - petId?: number; + 'petId'?: number; @Property.number({ required: false }) - quantity?: number; + 'quantity'?: number; @Property.date({ required: false }) - shipDate?: Date; + 'shipDate'?: Date; @Property.string({ required: false }) - status?: 'placed' | 'approved' | 'delivered'; + 'status'?: 'placed' | 'approved' | 'delivered'; @Property.boolean({ required: false }) - complete?: boolean; + 'complete'?: boolean; } \ No newline at end of file diff --git a/sandbox/src/models/generated/petstore/petstore.pet.model.ts b/sandbox/src/models/generated/petstore/petstore.pet.model.ts index ef3e6ca..e00be1e 100644 --- a/sandbox/src/models/generated/petstore/petstore.pet.model.ts +++ b/sandbox/src/models/generated/petstore/petstore.pet.model.ts @@ -1,25 +1,24 @@ -import { Property } from 'zibri'; - -import { PetstoreCategory } from './petstore.category.model'; import { PetstoreTag } from './petstore.tag.model'; +import { PetstoreCategory } from './petstore.category.model'; +import { Property } from 'zibri'; export class PetstorePet { @Property.number({ required: false }) - id?: number; + 'id'?: number; @Property.object({ required: false, cls: () => PetstoreCategory }) - category?: PetstoreCategory; + 'category'?: PetstoreCategory; @Property.string() - name!: string; + 'name'!: string; @Property.array({ items: { type: 'string' } }) - photoUrls!: string[]; + 'photoUrls'!: string[]; @Property.array({ required: false, items: { type: 'object', cls: () => PetstoreTag } }) - tags?: PetstoreTag[]; + 'tags'?: PetstoreTag[]; @Property.string({ required: false }) - status?: 'available' | 'pending' | 'sold'; + 'status'?: 'available' | 'pending' | 'sold'; } \ No newline at end of file diff --git a/sandbox/src/models/generated/petstore/petstore.tag.model.ts b/sandbox/src/models/generated/petstore/petstore.tag.model.ts index 17ad3b5..1bc26dc 100644 --- a/sandbox/src/models/generated/petstore/petstore.tag.model.ts +++ b/sandbox/src/models/generated/petstore/petstore.tag.model.ts @@ -3,8 +3,8 @@ import { Property } from 'zibri'; export class PetstoreTag { @Property.number({ required: false }) - id?: number; + 'id'?: number; @Property.string({ required: false }) - name?: string; + 'name'?: string; } \ No newline at end of file diff --git a/sandbox/src/models/generated/petstore/petstore.update-pet-with-form.model.ts b/sandbox/src/models/generated/petstore/petstore.update-pet-with-form.model.ts deleted file mode 100644 index f49a3a5..0000000 --- a/sandbox/src/models/generated/petstore/petstore.update-pet-with-form.model.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Property } from 'zibri'; - -export class PetstoreUpdatePetWithForm { - - @Property.string({ required: false }) - name?: string; - - @Property.string({ required: false }) - status?: string; -} \ No newline at end of file diff --git a/sandbox/src/models/generated/petstore/petstore.upload-file.model.ts b/sandbox/src/models/generated/petstore/petstore.upload-file.model.ts deleted file mode 100644 index 6176b9d..0000000 --- a/sandbox/src/models/generated/petstore/petstore.upload-file.model.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Property } from 'zibri'; - -export class PetstoreUploadFile { - - @Property.string({ required: false }) - additionalMetadata?: string; - - @Property.string({ required: false }) - file?: string; -} \ No newline at end of file diff --git a/sandbox/src/models/generated/petstore/petstore.user.model.ts b/sandbox/src/models/generated/petstore/petstore.user.model.ts index 75b08e3..5995310 100644 --- a/sandbox/src/models/generated/petstore/petstore.user.model.ts +++ b/sandbox/src/models/generated/petstore/petstore.user.model.ts @@ -3,26 +3,26 @@ import { Property } from 'zibri'; export class PetstoreUser { @Property.number({ required: false }) - id?: number; + 'id'?: number; @Property.string({ required: false }) - username?: string; + 'username'?: string; @Property.string({ required: false }) - firstName?: string; + 'firstName'?: string; @Property.string({ required: false }) - lastName?: string; + 'lastName'?: string; @Property.string({ required: false }) - email?: string; + 'email'?: string; @Property.string({ required: false }) - password?: string; + 'password'?: string; @Property.string({ required: false }) - phone?: string; + 'phone'?: string; @Property.number({ required: false }) - userStatus?: number; + 'userStatus'?: number; } \ No newline at end of file diff --git a/sandbox/src/models/user.model.ts b/sandbox/src/models/user.model.ts index da26f99..8903948 100644 --- a/sandbox/src/models/user.model.ts +++ b/sandbox/src/models/user.model.ts @@ -9,10 +9,10 @@ export class User extends BaseUserEntity(Roles) { name!: string; @Property.manyToOne({ target: () => Company, inverseSide: 'workers', joinColumn: 'companyId', required: false }) - company?: Company; + company?: Company | null; @Property.string({ format: 'uuid', required: false }) - companyId?: string; + companyId?: string | null; } export class UserCreateDto extends IntersectionClass( diff --git a/sandbox/versions/1.0.0.json b/sandbox/versions/1.0.0.json new file mode 100644 index 0000000..201b57b --- /dev/null +++ b/sandbox/versions/1.0.0.json @@ -0,0 +1 @@ +{"startsAt":"2026-05-27T08:11:12.332Z","value":"1.0.0","routes":[{"key":"GET /favicon.ico","versions":"all"},{"key":"GET /explorer/spec/:version","versions":"all"},{"key":"GET /explorer/swagger-ui.css","versions":"all"},{"key":"GET /explorer/swagger-ui-bundle.js","versions":"all"},{"key":"GET /explorer/swagger-ui-standalone-preset.js","versions":"all"},{"key":"GET /explorer/swagger-ui-init.js","versions":"all"},{"key":"GET /explorer/custom.js","versions":"all"},{"key":"GET /explorer","versions":"all"},{"versions":["latest"],"key":"GET /tests/"},{"versions":["latest"],"key":"GET /tests/:id"},{"versions":["latest"],"key":"POST /tests/"},{"versions":["latest"],"key":"PATCH /tests/:id"},{"versions":["latest"],"key":"DELETE /tests/:id"},{"versions":["latest"],"key":"POST /files/"},{"versions":["latest"],"key":"GET /files/stream"},{"versions":["latest"],"key":"GET /templates/socket"},{"versions":["latest"],"key":"GET /templates/password-reset-mail"},{"versions":["latest"],"key":"GET /templates/log"},{"versions":["latest"],"key":"GET /cron/"},{"versions":["latest"],"key":"POST /cron/enable-status"},{"versions":["latest"],"key":"POST /cron/disable-status"},{"versions":["latest"],"key":"POST /auth/login"},{"versions":["latest"],"key":"POST /auth/refresh-login"},{"versions":["latest"],"key":"POST /auth/request-password-reset"},{"versions":["latest"],"key":"POST /auth/verify-password-reset-token"},{"versions":["latest"],"key":"POST /auth/confirm-password-reset"},{"versions":["latest"],"key":"POST /auth/logout"},{"versions":["latest"],"key":"GET /metrics/"},{"versions":["latest"],"key":"GET /metrics/dashboard"},{"versions":["latest"],"key":"POST /tests-crud/"},{"versions":["latest"],"key":"GET /tests-crud/"},{"versions":["latest"],"key":"GET /tests-crud/:id"},{"versions":["latest"],"key":"PATCH /tests-crud/:id"},{"versions":["latest"],"key":"GET /tests-crud/password-reset-mail"},{"versions":["latest"],"key":"GET /"},{"versions":["latest"],"key":"GET /assets"},{"versions":["latest"],"key":"GET /mailing-lists/:id/subscribe/:token"},{"versions":["latest"],"key":"GET /mailing-lists/:id/unsubscribe"},{"versions":["latest"],"key":"GET /mailing-lists/preferences"},{"versions":["latest"],"key":"PATCH /mailing-lists/preferences"},{"versions":["latest"],"key":"chat message"}],"endsAt":"2026-05-27T08:19:29.305Z"} \ No newline at end of file diff --git a/sandbox/versions/1.0.1.json b/sandbox/versions/1.0.1.json new file mode 100644 index 0000000..5c38bc6 --- /dev/null +++ b/sandbox/versions/1.0.1.json @@ -0,0 +1 @@ +{"value":"1.0.1","startsAt":"2026-05-27T08:19:29.305Z","routes":[{"key":"GET /favicon.ico","versions":"all"},{"key":"GET /explorer/spec/:version","versions":"all"},{"key":"GET /explorer/swagger-ui.css","versions":"all"},{"key":"GET /explorer/swagger-ui-bundle.js","versions":"all"},{"key":"GET /explorer/swagger-ui-standalone-preset.js","versions":"all"},{"key":"GET /explorer/swagger-ui-init.js","versions":"all"},{"key":"GET /explorer/custom.js","versions":"all"},{"key":"GET /explorer","versions":"all"},{"versions":"all","key":"GET /tests/"},{"versions":"all","key":"GET /tests/:id"},{"versions":"all","key":"POST /tests/"},{"versions":"all","key":"PATCH /tests/:id"},{"versions":"all","key":"DELETE /tests/:id"},{"versions":"all","key":"POST /files/"},{"versions":"all","key":"GET /files/stream"},{"versions":"all","key":"GET /templates/socket"},{"versions":"all","key":"GET /templates/password-reset-mail"},{"versions":"all","key":"GET /templates/log"},{"versions":"all","key":"GET /cron/"},{"versions":"all","key":"POST /cron/enable-status"},{"versions":"all","key":"POST /cron/disable-status"},{"versions":"all","key":"POST /auth/login"},{"versions":"all","key":"POST /auth/refresh-login"},{"versions":"all","key":"POST /auth/request-password-reset"},{"versions":"all","key":"POST /auth/verify-password-reset-token"},{"versions":"all","key":"POST /auth/confirm-password-reset"},{"versions":"all","key":"POST /auth/logout"},{"versions":"all","key":"GET /metrics/"},{"versions":"all","key":"GET /metrics/dashboard"},{"versions":"all","key":"POST /tests-crud/"},{"versions":"all","key":"GET /tests-crud/"},{"versions":"all","key":"GET /tests-crud/:id"},{"versions":"all","key":"PATCH /tests-crud/:id"},{"versions":"all","key":"GET /tests-crud/password-reset-mail"},{"versions":"all","key":"GET /"},{"versions":"all","key":"GET /assets"},{"versions":"all","key":"GET /mailing-lists/:id/subscribe/:token"},{"versions":"all","key":"GET /mailing-lists/:id/unsubscribe"},{"versions":"all","key":"GET /mailing-lists/preferences"},{"versions":"all","key":"PATCH /mailing-lists/preferences"},{"versions":"all","key":"chat message"}]} \ No newline at end of file diff --git a/sandbox/webpack.config.js b/sandbox/webpack.config.js index 4a4e91a..5d3fb9e 100644 --- a/sandbox/webpack.config.js +++ b/sandbox/webpack.config.js @@ -178,13 +178,15 @@ module.exports = { patterns: [ { from: path.resolve(__dirname, 'assets'), - to: path.resolve(__dirname, 'dist', 'assets'), - noErrorOnMissing: true + to: path.resolve(__dirname, 'dist', 'assets') }, { from: path.resolve(__dirname, 'src', 'templates'), - to: path.resolve(__dirname, 'dist', 'assets', 'templates'), - noErrorOnMissing: true + to: path.resolve(__dirname, 'dist', 'assets', 'templates') + }, + { + from: path.resolve(__dirname, 'versions'), + to: path.resolve(__dirname, 'dist', 'versions') } ] }) diff --git a/src/__testing__/test-server/start-test-server.function.ts b/src/__testing__/test-server/start-test-server.function.ts index 788ef00..4e1e870 100644 --- a/src/__testing__/test-server/start-test-server.function.ts +++ b/src/__testing__/test-server/start-test-server.function.ts @@ -15,6 +15,8 @@ import { PostgresDataSource, PostgresOptions } from '../../data-source/data-sour import { ZIBRI_DI_TOKENS } from '../../di/default/zibri-di-tokens.default'; import { DiContainer } from '../../di/di-container'; import { inject } from '../../di/inject.function'; +import { AppState } from '../../global/app-state.enum'; +import { GlobalRegistry } from '../../global/global-registry'; import { LoggerInterface } from '../../logging/logger.interface'; import { Newable } from '../../types/newable.type'; import { noOp, POSTGRES_TEST_IMAGE, testAssetsFolder } from '../constants'; @@ -22,13 +24,23 @@ import { createTestDataSource } from './create-test-data-source.function'; import { AssetServiceInterface } from '../../assets/asset-service.interface'; import { OmitStrict } from '../../types/omit-strict.type'; -type StartTestServerOptions = Partial> & { +type StartTestServerOptions = Partial< + Pick< + ZibriApplicationOptions, + 'providers' + | 'plugins' + | 'controllers' + | 'cronJobs' + | 'version' + | 'websocketControllers' + > +> & { dataSources?: Newable[] }; export class StartedTestServer { constructor( - private readonly app: ZibriApplication, + private app: ZibriApplication, private readonly containers: AbstractStartedContainer[], private readonly exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never) ) {} @@ -50,6 +62,46 @@ export class StartedTestServer { this.exitSpy.mockRestore(); await Promise.all(this.containers.map(c => c.stop())); } + + async reInit( + { + dataSources = this.app.options.dataSources as Newable[], + providers = this.app.options.providers, + plugins = defaultTestServerPlugins, + controllers = this.app.options.controllers, + websocketControllers = this.app.options.websocketControllers, + cronJobs = this.app.options.cronJobs, + version = this.app.options.version + }: StartTestServerOptions = {} + ): Promise { + await this.app.shutdown(); + + // Reset singleton — every test file gets a clean container with no stale instances. + DiContainer['singleton'] = undefined; + GlobalRegistry['appData'].state = AppState.OFFLINE; + + const logger: LoggerInterface = inject(ZIBRI_DI_TOKENS.LOGGER); + await logger.info('re initializes test server...'); + const info: typeof logger.info = logger.info; + logger.info = noOp; + + this.app = new ZibriApplication({ + name: 'test', + version, + baseUrl: 'http://localhost:3000', + controllers, + websocketControllers, + dataSources, + providers, + plugins, + cronJobs + }); + + await this.app.init(H); + + logger.info = info; + await logger.info('test server re initialized'); + } } export async function startTestServer( @@ -58,7 +110,9 @@ export async function startTestServer( providers = defaultTestServerProviders, plugins = defaultTestServerPlugins, controllers = [], - cronJobs = [] + websocketControllers = [], + cronJobs = [], + version = '1.0.0' }: StartTestServerOptions = {} ): Promise { // Reset singleton — every test file gets a clean container with no stale instances. @@ -79,7 +133,7 @@ export async function startTestServer( })); const logger: LoggerInterface = inject(ZIBRI_DI_TOKENS.LOGGER); - await logger.info('starts up test server...'); + await logger.info('initializes test server...'); const info: typeof logger.info = logger.info; logger.info = noOp; @@ -88,10 +142,10 @@ export async function startTestServer( const app: ZibriApplication = new ZibriApplication({ name: 'test', - version: '0.0.1', + version, baseUrl: 'http://localhost:3000', controllers, - websocketControllers: [], + websocketControllers, dataSources, providers, plugins, @@ -101,7 +155,7 @@ export async function startTestServer( await app.init(H); logger.info = info; - await logger.info('test server started'); + await logger.info('test server initialized'); return new StartedTestServer(app, containers); } \ No newline at end of file diff --git a/src/application-options.model.ts b/src/application-options.model.ts index e51c9cb..b11cef8 100644 --- a/src/application-options.model.ts +++ b/src/application-options.model.ts @@ -8,7 +8,7 @@ import { BodyParserInterface } from './parsing/body-parser.interface'; import { ZibriPlugin } from './plugin/plugin.model'; import { DeepPartial } from './types/deep-partial.type'; import { Newable } from './types/newable.type'; -import { Version } from './types/version.type'; +import { SemVerVersion } from './utilities/sem-ver.utilities'; /** * Configuration options for strict transport security / hsts. @@ -61,7 +61,7 @@ export type ZibriApplicationOptions = { * * Is also used by migrations by default. */ - version: Version, + version: SemVerVersion, /** * The controllers to register in the app. */ diff --git a/src/application.ts b/src/application.ts index 0bcffd9..7006b16 100644 --- a/src/application.ts +++ b/src/application.ts @@ -428,7 +428,6 @@ export class ZibriApplication { } }; for (const plugin of this.providedOptions.plugins ?? []) { - // TODO: handle order of plugin initialization so that everything is available for DI inside the plugin constructor. res.authStrategies = [...plugin.authStrategies, ...res.authStrategies]; res.bodyParsers = [...plugin.bodyParsers, ...res.bodyParsers]; res.controllers = [...plugin.controllers, ...res.controllers]; diff --git a/src/assets/asset.service.ts b/src/assets/asset.service.ts index 0b74951..32e9ad5 100644 --- a/src/assets/asset.service.ts +++ b/src/assets/asset.service.ts @@ -58,7 +58,9 @@ export class AssetService implements AssetServiceInterface, OnAppInit { await inject(ZIBRI_DI_TOKENS.ROUTER).registerRoute({ httpMethod: HttpMethod.GET, route: '/favicon.ico', - handler: () => FileResponse.fromPath(FsUtilities.getPath(this.publicAssetsPath, 'favicon.png')) + handler: () => FileResponse.fromPath(FsUtilities.getPath(this.publicAssetsPath, 'favicon.png')), + versions: 'all', + openApi: { useInOpenApi: false } }); } diff --git a/src/auth/2fa/methods/otp/otp-credentials.model.ts b/src/auth/2fa/methods/otp/otp-credentials.model.ts index c900fd3..c14ab90 100644 --- a/src/auth/2fa/methods/otp/otp-credentials.model.ts +++ b/src/auth/2fa/methods/otp/otp-credentials.model.ts @@ -27,7 +27,7 @@ export class OtpCredentials extends BaseEntity { * The two factor url that is needed to display a qr code. */ @Property.string({ required: false }) - qrCodeUrl: string | undefined; + qrCodeUrl: string | undefined | null; } /** diff --git a/src/auth/2fa/methods/otp/otp.two-factor-method.ts b/src/auth/2fa/methods/otp/otp.two-factor-method.ts index 4f8f4ed..ca880f9 100644 --- a/src/auth/2fa/methods/otp/otp.two-factor-method.ts +++ b/src/auth/2fa/methods/otp/otp.two-factor-method.ts @@ -84,7 +84,7 @@ export class OtpTwoFactorMethod implements TwoFactorMethod, diff --git a/src/auth/strategies/jwt/jwt-auth.controller.ts b/src/auth/strategies/jwt/jwt-auth.controller.ts index e76bb9b..3f8742c 100644 --- a/src/auth/strategies/jwt/jwt-auth.controller.ts +++ b/src/auth/strategies/jwt/jwt-auth.controller.ts @@ -56,7 +56,7 @@ class JwtConfirmPasswordResetDto extends OmitClass(JwtConfirmPasswordResetData, class JwtRefreshLoginDto extends OmitClass(JwtRefreshLoginData, ['transaction']) {} -@Controller('/auth', { allowOrphan: true }) +@Controller('/auth', { allowOrphan: true, versions: 'all' }) export class JwtAuthController implements AuthControllerInterface< JwtCredentialsDto, JwtAuthData, diff --git a/src/auth/strategies/jwt/jwt.auth-strategy.ts b/src/auth/strategies/jwt/jwt.auth-strategy.ts index 796c7f6..877d30c 100644 --- a/src/auth/strategies/jwt/jwt.auth-strategy.ts +++ b/src/auth/strategies/jwt/jwt.auth-strategy.ts @@ -383,7 +383,7 @@ implements AuthStrategyInterface< private extractAccessTokenFromRequestContext( context: HttpRequestContext | WebsocketRequestContext ): string | undefined { - const authHeader: string | string[] | undefined = context.request.headers.authorization; + const authHeader: string | string[] | undefined = context.request.headers?.authorization; if (authHeader == undefined || typeof authHeader !== 'string') { return undefined; } diff --git a/src/backup/backup-resource-entity.model.ts b/src/backup/backup-resource-entity.model.ts index 1623f9a..15923e5 100644 --- a/src/backup/backup-resource-entity.model.ts +++ b/src/backup/backup-resource-entity.model.ts @@ -18,7 +18,7 @@ export class BackupResourceEntity extends BaseEntity { * The size of the resource in bytes. */ @Property.number({ required: false }) - size: number | undefined; + size: number | undefined | null; /** * Whether or not the backup of this resource has been completed. */ diff --git a/src/backup/backup-service.test.ts b/src/backup/backup-service.test.ts index 7c3ce93..0dc1aea 100644 --- a/src/backup/backup-service.test.ts +++ b/src/backup/backup-service.test.ts @@ -17,6 +17,7 @@ import { BaseEntity } from '../entity/base-entity.model'; import { Entity } from '../entity/decorators/entity.decorator'; import { Property } from '../entity/decorators/property.decorator'; import { Newable } from '../types/newable.type'; +import { OmitStrict } from '../types/omit-strict.type'; import { FsUtilities, FsPath } from '../utilities/fs.utilities'; const backupFsFolder: FsPath = FsUtilities.getPath(testFileFolder, 'backups'); @@ -42,7 +43,7 @@ class Item { class DbDataSource extends PostgresDataSource { rootPw: string = 'password'; rootUsername: string = 'postgres'; - options: PostgresOptions = { + options: OmitStrict = { host: 'localhost', username: 'postgres', password: 'password', diff --git a/src/change-sets/models/change-set.model.ts b/src/change-sets/models/change-set.model.ts index d9449c0..c799d65 100644 --- a/src/change-sets/models/change-set.model.ts +++ b/src/change-sets/models/change-set.model.ts @@ -26,7 +26,7 @@ export class ChangeSet extends BaseEntity { * The id of the user that changed something. */ @Property.string({ format: 'uuid', required: false }) - createdBy?: string; + createdBy?: string | null; /** * The things that have been changed. */ diff --git a/src/change-sets/models/change.model.ts b/src/change-sets/models/change.model.ts index 4f28841..5043ba7 100644 --- a/src/change-sets/models/change.model.ts +++ b/src/change-sets/models/change.model.ts @@ -18,12 +18,12 @@ export class Change extends BaseEntity { * The value before it was changed. */ @Property.unknown({ required: false }) - previousValue?: T; + previousValue?: T | null; /** * The value after it was changed. */ @Property.unknown({ required: false }) - newValue?: T; + newValue?: T | null; /** * The change set that this change belongs to. */ diff --git a/src/context/cache/cache.context.ts b/src/context/cache/cache.context.ts index 15852ac..9d61de7 100644 --- a/src/context/cache/cache.context.ts +++ b/src/context/cache/cache.context.ts @@ -24,10 +24,10 @@ export class CacheContext { * Whether or not the cache has been hit. */ @Property.boolean({ required: false }) - hit?: boolean; + hit?: boolean | null; /** * The duration that the original function took. */ @Property.number({ required: false }) - durationInMs?: number; + durationInMs?: number | null; } \ No newline at end of file diff --git a/src/context/request/request-context-token.model.ts b/src/context/request/request-context-token.model.ts index 1528009..33f883f 100644 --- a/src/context/request/request-context-token.model.ts +++ b/src/context/request/request-context-token.model.ts @@ -51,7 +51,7 @@ export const ZIBRI_REQUEST_CONTEXT_TOKENS = { 'correlation_id', ctx => { const correlationIdHeader: string = inject(ZIBRI_DI_TOKENS.CORRELATION_ID_HEADER); - return ctx.request.headers[correlationIdHeader as KnownHeader] ?? UUIDUtilities.generate(); + return ctx.request.headers?.[correlationIdHeader as KnownHeader] ?? UUIDUtilities.generate(); } ), CURRENT_USER: new RequestContextToken( diff --git a/src/cron/cron-job-entity.model.ts b/src/cron/cron-job-entity.model.ts index 7b86f26..6c73c9b 100644 --- a/src/cron/cron-job-entity.model.ts +++ b/src/cron/cron-job-entity.model.ts @@ -43,13 +43,13 @@ export class CronJobEntity extends BaseEntity { * The timestamp at which this cron job has been last run. */ @Property.date({ required: false }) - lastRun!: Date | undefined; + lastRun?: Date | null; /** * The error message that this cron job failed with. */ @Property.string({ required: false }) - errorMessage!: string | undefined; + errorMessage?: string | null; } /** diff --git a/src/data-source/data-sources/typeorm-base-data-source.model.ts b/src/data-source/data-sources/typeorm-base-data-source.model.ts index ba03d7e..3c1624c 100644 --- a/src/data-source/data-sources/typeorm-base-data-source.model.ts +++ b/src/data-source/data-sources/typeorm-base-data-source.model.ts @@ -28,10 +28,9 @@ import { type LoggerInterface } from '../../logging/logger.interface'; import { ExcludeStrict } from '../../types/exclude-strict.type'; import { Newable } from '../../types/newable.type'; import { OmitStrict } from '../../types/omit-strict.type'; -import { Version } from '../../types/version.type'; -import { compareVersion } from '../../utilities/compare-versions.function'; import { MetadataUtilities } from '../../utilities/metadata.utilities'; import { ObjectUtilities } from '../../utilities/object.utilities'; +import { SemVerUtilities, SemVerVersion } from '../../utilities/sem-ver.utilities'; import { TypeOrmUtilities } from '../../utilities/typeorm.utilities'; import { getDefaultBeforeReturnHook, getDefaultBeforeSaveHook } from '../hooks/hooks.default'; import { MigrationEntity } from '../migration/migration-entity.model'; @@ -217,19 +216,19 @@ export abstract class TypeOrmBaseDataSource const finishedMigrationVersions: string[] = (await migrationsRepository.findAll()).map(m => m.version); const allMigrations: MigrationWithName[] = this.migrations.map(m => ({ migration: inject(m), name: m.name })); - const appVersion: Version | undefined = GlobalRegistry.getAppData('version'); + const appVersion: SemVerVersion | undefined = GlobalRegistry.getAppData('version'); if (!appVersion) { throw new Error('Couldn\'t run migrations: No app version could be resolved'); } const migrationsToRunUp: MigrationWithName[] = allMigrations.filter(m => { return !finishedMigrationVersions.includes(m.migration.version) - && compareVersion(m.migration.version, appVersion) !== 'bigger'; + && SemVerUtilities.compare(m.migration.version, appVersion) !== 'bigger'; }); const migrationsToRunDown: MigrationWithName[] = allMigrations.filter(m => { return finishedMigrationVersions.includes(m.migration.version) - && compareVersion(m.migration.version, appVersion) === 'bigger'; + && SemVerUtilities.compare(m.migration.version, appVersion) === 'bigger'; }); for (const migration of migrationsToRunUp) { diff --git a/src/data-source/data-sources/where-converter/typeorm-where-filter.converter.ts b/src/data-source/data-sources/where-converter/typeorm-where-filter.converter.ts index 7ea53bf..1249499 100644 --- a/src/data-source/data-sources/where-converter/typeorm-where-filter.converter.ts +++ b/src/data-source/data-sources/where-converter/typeorm-where-filter.converter.ts @@ -54,6 +54,8 @@ export abstract class TypeOrmWhereFilterConverter { }, after: (value) => MoreThan(value), before: (value) => LessThan(value), + afterOrOn: (value) => MoreThanOrEqual(value), + beforeOrOn: (value) => LessThanOrEqual(value), greaterThan: (value) => MoreThan(value), greaterThanEquals: (value) => MoreThanOrEqual(value), lesserThan: (value) => LessThan(value), @@ -67,7 +69,6 @@ export abstract class TypeOrmWhereFilterConverter { if ( (metadata.type === Relation.MANY_TO_ONE || metadata.type === Relation.BELONGS_TO_ONE) - && value !== null && typeof value === 'object' && !Array.isArray(value) && 'id' in value diff --git a/src/data-source/hooks/hooks.test.ts b/src/data-source/hooks/hooks.test.ts index fadf9f3..b8ce19b 100644 --- a/src/data-source/hooks/hooks.test.ts +++ b/src/data-source/hooks/hooks.test.ts @@ -33,7 +33,7 @@ class HookTestEntity extends BaseEntity implements ChangeSetEntity, SoftDeleteEn isActive!: boolean; // default value set on create @Property.string({ required: false, excludeFromChangeSets: true }) - internalNote?: string; // excluded from change sets + internalNote?: string | null; // excluded from change sets @Property.boolean({ default: false }) deleted!: boolean; // required by SoftDeleteEntity diff --git a/src/data-source/migration/migration-entity.model.ts b/src/data-source/migration/migration-entity.model.ts index cee2ea9..f93fd6d 100644 --- a/src/data-source/migration/migration-entity.model.ts +++ b/src/data-source/migration/migration-entity.model.ts @@ -1,7 +1,7 @@ import { BaseEntity } from '../../entity/base-entity.model'; import { Entity } from '../../entity/decorators/entity.decorator'; import { Property } from '../../entity/decorators/property.decorator'; -import { type Version } from '../../types/version.type'; +import { type SemVerVersion } from '../../utilities/sem-ver.utilities'; /** * The migration entity that is stored in the db. @@ -18,7 +18,7 @@ export class MigrationEntity extends BaseEntity { * The version at which this migration should run. */ @Property.string({ unique: true }) - version!: Version; + version!: SemVerVersion; /** * The timestamp at which the migration ran. diff --git a/src/data-source/migration/migration.model.ts b/src/data-source/migration/migration.model.ts index 40cf79b..eec4ee2 100644 --- a/src/data-source/migration/migration.model.ts +++ b/src/data-source/migration/migration.model.ts @@ -2,7 +2,7 @@ import { MigrationEntity } from './migration-entity.model'; import { repositoryTokenFor } from '../../di/decorators/inject-repository.decorator'; import { inject } from '../../di/inject.function'; import { Newable } from '../../types/newable.type'; -import { Version } from '../../types/version.type'; +import { SemVerVersion } from '../../utilities/sem-ver.utilities'; import { DataSourceInterface } from '../data-sources/data-source.interface'; import { Repository } from '../repository'; import { Transaction } from '../transaction/transaction.model'; @@ -11,7 +11,7 @@ import { Transaction } from '../transaction/transaction.model'; * Base class for a data source migration. */ export abstract class Migration { - abstract readonly version: Version; + abstract readonly version: SemVerVersion; /** * The data source that the migration is for. */ diff --git a/src/data-source/migration/migration.test.ts b/src/data-source/migration/migration.test.ts index ecabdbf..a5a7196 100644 --- a/src/data-source/migration/migration.test.ts +++ b/src/data-source/migration/migration.test.ts @@ -20,7 +20,7 @@ import { Property } from '../../entity/decorators/property.decorator'; import { GlobalRegistry } from '../../global/global-registry'; import { Newable } from '../../types/newable.type'; import { OmitStrict } from '../../types/omit-strict.type'; -import { Version } from '../../types/version.type'; +import { SemVerVersion } from '../../types/version.type'; import { PostgresDataSource, PostgresOptions } from '../data-sources/postgres-typeorm-data-source.model'; import { DataSource } from '../decorators/data-source.decorator'; import { Repository } from '../repository'; @@ -68,7 +68,7 @@ class DbDataSource extends PostgresDataSource { @Injectable() class AddTestValueMigration extends Migration { - version: Version = '0.0.1'; + version: SemVerVersion = '0.0.1'; constructor( @InjectRepository(Item) diff --git a/src/data-source/models/where/date-where-filter.model.ts b/src/data-source/models/where/date-where-filter.model.ts index 317c51f..8917d95 100644 --- a/src/data-source/models/where/date-where-filter.model.ts +++ b/src/data-source/models/where/date-where-filter.model.ts @@ -5,13 +5,21 @@ import { BaseWhereFilter, BaseWhereFilterObject } from './base-where-filter.mode */ type DateFilterWhereObject = BaseWhereFilterObject & { /** - * The property needs to be after the value. + * The property needs to be after the date. */ after?: Date, /** - * The property needs to be before the value. + * The property needs to be before the date. */ - before?: Date + before?: Date, + /** + * The property needs to be after or on the date. + */ + afterOrOn?: Date, + /** + * The property needs to be before or on the date. + */ + beforeOrOn?: Date }; /** diff --git a/src/data-source/models/where/where-filter-keys.model.ts b/src/data-source/models/where/where-filter-keys.model.ts index 59ab4da..120710b 100644 --- a/src/data-source/models/where/where-filter-keys.model.ts +++ b/src/data-source/models/where/where-filter-keys.model.ts @@ -60,7 +60,9 @@ const whereFilterKeysRecord: Record = { oneOf: 'oneOf', notOneOf: 'notOneOf', after: 'after', + afterOrOn: 'afterOrOn', before: 'before', + beforeOrOn: 'beforeOrOn', greaterThan: 'greaterThan', greaterThanEquals: 'greaterThanEquals', lesserThan: 'lesserThan', diff --git a/src/data-source/nested-where-filter.test.ts b/src/data-source/nested-where-filter.test.ts index a22bf10..9a9ff7c 100644 --- a/src/data-source/nested-where-filter.test.ts +++ b/src/data-source/nested-where-filter.test.ts @@ -20,7 +20,7 @@ class Address { city!: string; @Property.string({ required: false }) - type?: string; // 'home', 'work', etc. + type?: string | null; // 'home', 'work', etc. } class PersonalData { @@ -182,7 +182,6 @@ describe('Where filters – deeply nested (object → array → object)', () => // ===== Array of objects: includes (contains) – requires feature, skip for now ===== it('array includes (contains all specified objects)', async () => { - // TODO: implement partial object match in includes for object arrays const res: Customer[] = await customerRepo.findAll({ where: { personalData: { @@ -197,7 +196,6 @@ describe('Where filters – deeply nested (object → array → object)', () => // ===== Array of objects: nested where on items (if supported) ===== it('array item where filter', async () => { - // TODO: implement 'where' on array items to filter by item properties const res: Customer[] = await customerRepo.findAll({ where: { personalData: { diff --git a/src/data-source/repository.test.ts b/src/data-source/repository.test.ts index bfc73e7..ae2f37e 100644 --- a/src/data-source/repository.test.ts +++ b/src/data-source/repository.test.ts @@ -87,7 +87,7 @@ class User extends BaseEntity { company!: Company; @Property.string({ format: 'uuid', required: false }) - companyId!: string; + companyId?: string | null; @Property.oneToMany({ target: () => Post, inverseSide: 'author' }) posts!: Post[]; @@ -96,7 +96,7 @@ class User extends BaseEntity { profile!: Profile; @Property.string({ format: 'uuid', required: false }) - profileId!: string; + profileId?: string | null; @Property.manyToMany({ target: () => Group, inverseSide: 'members', joinTable: true }) groups!: Group[]; diff --git a/src/data-source/where-filter.test.ts b/src/data-source/where-filter.test.ts index 5c5a026..d563885 100644 --- a/src/data-source/where-filter.test.ts +++ b/src/data-source/where-filter.test.ts @@ -406,13 +406,6 @@ describe('Where filters', () => { const res: Product[] = await productRepo.findAll({ where: { category: { is: catFood } } }); expect(res.map(p => p.id)).toEqual([prodFood.id]); }); - - // TODO - // it('null (no category)', async () => { - // await productRepo.updateById(prodFood.id, { category: null }); - // const res: Product[] = await productRepo.findAll({ where: { category: null } }); - // expect(res.map(p => p.id)).toEqual([prodFood.id]); - // }); }); // =================== ARRAY OF OBJECTS (Review[]) =================== diff --git a/src/di/default/zibri-di-providers.default.ts b/src/di/default/zibri-di-providers.default.ts index dfe9cad..ae6619d 100644 --- a/src/di/default/zibri-di-providers.default.ts +++ b/src/di/default/zibri-di-providers.default.ts @@ -25,6 +25,7 @@ import { DataSourceService } from '../../data-source/data-source.service'; import { EmailService } from '../../email/email.service'; import { errorHandler } from '../../error-handling/error-handler'; import { EventService } from '../../event/event.service'; +import { KnownHeader } from '../../http/known-header.enum'; import { HttpClient } from '../../http-client/http-client'; import { LocalizeOptionsInput } from '../../localization/models/localize-options.model'; import { LogLevel } from '../../logging/log-level.enum'; @@ -39,6 +40,7 @@ import { Router } from '../../routing/router'; import { FsUtilities } from '../../utilities/fs.utilities'; import { Ms } from '../../utilities/ms'; import { ValidationService } from '../../validation/validation.service'; +import { VersioningService } from '../../versioning/versioning.service'; import { WebsocketService } from '../../websocket/services/websocket.service'; import { DiTokenProviderRecord } from '../models/di-token.model'; @@ -147,6 +149,8 @@ export const ZIBRI_DI_PROVIDERS: DiTokenProviderRecord = ENCRYPTION_STRATEGIES: { useValue: [AesGcmEncryptionStrategy] }, ENCRYPTION_MASTER_OPTIONS: { useValue: undefined }, CACHE_SERVICE: { useClass: CacheService }, + VERSIONING_SERVICE: { useClass: VersioningService }, + VERSION_HEADER: { useValue: KnownHeader.X_VERSION }, // dynamic CURRENT_REQUEST_CONTEXT: { useFactory: () => AlsUtilities.getCurrentRequestContext(), diff --git a/src/di/default/zibri-di-tokens.default.ts b/src/di/default/zibri-di-tokens.default.ts index e09ae32..64be092 100644 --- a/src/di/default/zibri-di-tokens.default.ts +++ b/src/di/default/zibri-di-tokens.default.ts @@ -38,6 +38,7 @@ import { RouterInterface } from '../../routing/router.interface'; import { Newable } from '../../types/newable.type'; import { FsPath } from '../../utilities/fs.utilities'; import { ValidationServiceInterface } from '../../validation/validation-service.interface'; +import { VersioningServiceInterface } from '../../versioning/versioning-service.interface'; import { WebsocketOptions } from '../../websocket/models/websocket-options.model'; import { WebsocketServiceInterface } from '../../websocket/services/websocket-service.interface'; import { TokenRecord } from '../models/di-token.model'; @@ -115,6 +116,8 @@ export const ZIBRI_DI_TOKENS = { 'zi.encryption_master_options' ), CACHE_SERVICE: ziToken('zi.cache_service'), + VERSIONING_SERVICE: ziToken('zi.versioning_service'), + VERSION_HEADER: ziToken('zi.version_header'), // dynamic/context based tokens CURRENT_REQUEST_CONTEXT: ziToken('zi.current_request_context'), DEFAULT_CSP_OPTIONS: ziToken('zi.default_csp_options'), diff --git a/src/email/email.service.ts b/src/email/email.service.ts index 4f8e745..9ab580b 100644 --- a/src/email/email.service.ts +++ b/src/email/email.service.ts @@ -101,15 +101,15 @@ export class EmailService implements EmailServiceInterface, OnAppInit, OnAppShut * @param email - The email to send out. */ protected async send(email: Email): Promise { - await this.validateAttachments(email.attachments); + await this.validateAttachments(email.attachments ?? undefined); const res: SMTPTransport.SentMessageInfo = await this.transporter.sendMail({ html: email.html, subject: email.subject, from: email.sender, to: email.recipients, - bcc: email.bcc, - cc: email.cc, - attachments: email.attachments + bcc: email.bcc ?? undefined, + cc: email.cc ?? undefined, + attachments: email.attachments ?? undefined }); const status: EmailStatus = res.rejected.length ? EmailStatus.FAILED : EmailStatus.SENT; @@ -132,7 +132,7 @@ export class EmailService implements EmailServiceInterface, OnAppInit, OnAppShut * @param attachments - The attachments to resolve. * @returns The resolved attachments. */ - protected async validateAttachments(attachments: EmailAttachment[] | undefined): Promise { + protected async validateAttachments(attachments: EmailAttachment[] | undefined | null): Promise { if (!attachments?.length) { return; } diff --git a/src/email/models/email.model.ts b/src/email/models/email.model.ts index a2822a6..3d84482 100644 --- a/src/email/models/email.model.ts +++ b/src/email/models/email.model.ts @@ -20,7 +20,7 @@ export class Email extends BaseEntity { * Optional id of a userId to which this email belongs. */ @Property.string({ format: 'uuid', required: false }) - userId?: string; + userId?: string | null; /** * Whether or not the email should be stored in the db, even after it has been sent. @@ -56,19 +56,19 @@ export class Email extends BaseEntity { * The recipients that should receive the email as cc. */ @Property.array({ items: { type: 'string', format: 'email' }, required: false }) - cc?: string[]; + cc?: string[] | null; /** * The recipients that should receive the email as bcc. */ @Property.array({ items: { type: 'string', format: 'email' }, required: false }) - bcc?: string[]; + bcc?: string[] | null; /** * The attachments of the email. */ @Property.array({ items: { type: 'object', cls: () => EmailAttachment }, required: false }) - attachments?: EmailAttachment[]; + attachments?: EmailAttachment[] | null; /** * The emails status, like QUEUED, SENT or FAILED etc. diff --git a/src/entity/generation/generate-entity-file.function.ts b/src/entity/generation/generate-entity-file.function.ts index 5b13ad1..41621b8 100644 --- a/src/entity/generation/generate-entity-file.function.ts +++ b/src/entity/generation/generate-entity-file.function.ts @@ -176,7 +176,7 @@ function mapSchemaToDecoratorLines( } return [' @Property.date({ required: false })']; } - // TODO + // TODO handle enums in entity generation // if (schema.enum) { // return { type: schema.enum.map(v => JsonUtilities.stringify(v)).join(' | '), isRef: false }; // } diff --git a/src/event/event-subscriber-run.model.ts b/src/event/event-subscriber-run.model.ts index 0888600..984c94a 100644 --- a/src/event/event-subscriber-run.model.ts +++ b/src/event/event-subscriber-run.model.ts @@ -33,7 +33,7 @@ export class EventSubscriberRun extends BaseEntity { * The error property if the run failed. */ @Property.unknown({ required: false }) - error?: Error; + error?: Error | null; } /** diff --git a/src/global/global-registry.ts b/src/global/global-registry.ts index b7464b0..d43f25c 100644 --- a/src/global/global-registry.ts +++ b/src/global/global-registry.ts @@ -7,7 +7,7 @@ import { DiProvider } from '../di/models/di-provider.model'; import { BaseEntity } from '../entity/base-entity.model'; import { BodyParserInterface } from '../parsing/body-parser.interface'; import { Newable } from '../types/newable.type'; -import { Version } from '../types/version.type'; +import { SemVerVersion } from '../utilities/sem-ver.utilities'; /** * The data of the app. @@ -28,7 +28,7 @@ export type AppData = { /** * The current version of the app. */ - version?: Version + version?: SemVerVersion }; /** diff --git a/src/http-client/http-client.ts b/src/http-client/http-client.ts index 82c8e3f..f4f4ba4 100644 --- a/src/http-client/http-client.ts +++ b/src/http-client/http-client.ts @@ -1,4 +1,3 @@ - import axios, { AxiosInstance, AxiosResponse, isAxiosError, RawAxiosRequestConfig, ResponseType } from 'axios'; import { HttpClientResponse, HttpClientResponseForBodyType } from './http-client-response.model'; diff --git a/src/http/http-request.model.ts b/src/http/http-request.model.ts index 376e9d1..b37ae2c 100644 --- a/src/http/http-request.model.ts +++ b/src/http/http-request.model.ts @@ -1,7 +1,7 @@ import { Request } from 'express'; +import { Header } from './header.type'; import { HttpMethod } from './http-method.enum'; -import { KnownHeader } from './known-header.enum'; import { OmitStrict } from '../types/omit-strict.type'; /** @@ -11,7 +11,7 @@ export type HttpRequest< T = unknown, PathParamsObject extends Record = Record, QueryParamsObject extends Record = Record, - HeaderParamsObject extends Record = Partial> + HeaderParamsObject extends Record = Partial> // eslint-disable-next-line typescript/no-explicit-any > = OmitStrict, any, T>, 'query' | 'headers' | 'params' | 'method'> & { /** diff --git a/src/http/known-header.enum.ts b/src/http/known-header.enum.ts index ddd3c44..17301fb 100644 --- a/src/http/known-header.enum.ts +++ b/src/http/known-header.enum.ts @@ -26,6 +26,7 @@ export enum KnownHeader { X_FORWARDED_HOST = 'x-forwarded-host', X_FORWARDED_PROTO = 'x-forwarded-proto', X_REAL_IP = 'x-real-ip', + X_VERSION = 'x-version', IF_NONE_MATCH = 'if-none-match', IIF_MODIFIED_SINCE = 'if-modified-since', CONNECTION = 'connection', diff --git a/src/index.ts b/src/index.ts index 8b25753..5f04863 100644 --- a/src/index.ts +++ b/src/index.ts @@ -120,6 +120,7 @@ export * from './routing/decorators/get.decorator'; export * from './routing/decorators/post.decorator'; export * from './routing/decorators/delete.decorator'; export * from './routing/decorators/patch.decorator'; +export * from './routing/decorators/http-decorator-option-input.model'; export * from './routing/decorators/param.decorator'; export * from './routing/decorators/body.decorator'; @@ -541,6 +542,13 @@ export * from './caching/store/cache-store.interface'; export * from './caching/store/cached-value.model'; export * from './caching/store/in-memory.cache-store'; +// versioning +export * from './versioning/route-with-version-data.model'; +export * from './versioning/supported-versions-options.model'; +export * from './versioning/version.model'; +export * from './versioning/versioning-service.interface'; +export * from './versioning/versioning.service'; + // types export * from './types/any-enum.type'; export * from './types/deep-partial.type'; @@ -548,13 +556,11 @@ export * from './types/exclude-strict.type'; export * from './types/newable.type'; export * from './types/omit-strict.type'; export * from './types/percentage.type'; -export * from './types/version.type'; // utilities export * from './utilities/bytes'; export * from './utilities/doubly-linked-list'; -export * from './utilities/compare-versions.function'; -export * from './utilities/is-version.function'; +export * from './utilities/sem-ver.utilities'; export * from './utilities/now-in-ns.function'; export * from './utilities/promise.utilities'; export * from './utilities/ms'; diff --git a/src/logging/log-context.model.ts b/src/logging/log-context.model.ts index 4d5db3f..ccd4701 100644 --- a/src/logging/log-context.model.ts +++ b/src/logging/log-context.model.ts @@ -40,12 +40,12 @@ export class LogRequestContext { * The http status of the response, if the request already finished. */ @Property.number({ enum: HttpStatus, required: false }) - status?: HttpStatus; + status?: HttpStatus | null; /** * The duration that the request took in ms, if it already finished. */ @Property.number({ required: false }) - durationInMs?: number; + durationInMs?: number | null; } /** @@ -61,22 +61,22 @@ export class LogContext { * Any custom additional metadata for the log context. */ @Property.object({ cls: () => LogContextMetadata, required: false, allowAdditionalProperties: true }) - metadata?: LogContextMetadata; + metadata?: LogContextMetadata | null; /** * Context information about the request that triggered the log. */ @Property.object({ cls: () => LogRequestContext, required: false }) - request?: LogRequestContext; + request?: LogRequestContext | null; /** * Context information about the cache that triggered the log. */ @Property.array({ items: { type: 'object', cls: () => CacheContext }, required: false }) - cache?: CacheContext[]; + cache?: CacheContext[] | null; /** * An error associated to this log. */ @Property.object({ cls: () => LoggedError, required: false }) - error?: LoggedError; + error?: LoggedError | null; } /** diff --git a/src/logging/logger.ts b/src/logging/logger.ts index 7ec999b..4ae98ea 100644 --- a/src/logging/logger.ts +++ b/src/logging/logger.ts @@ -82,7 +82,7 @@ export class Logger implements LoggerInterface, OnAppInit { if (requestContext?.type === 'http-request') { request = { status: requestContext.request.res?.statusCode, - // TODO + // TODO: track duration on requests // durationInMs: currentRequest.res?.app, method: requestContext.request.method, url: requestContext.request.originalUrl, diff --git a/src/multithreading/models/thread-job-entity.model.ts b/src/multithreading/models/thread-job-entity.model.ts index fc149bd..bfefed9 100644 --- a/src/multithreading/models/thread-job-entity.model.ts +++ b/src/multithreading/models/thread-job-entity.model.ts @@ -33,11 +33,11 @@ export class ThreadJobEntity i /** * Timestamp of when the job was started in milliseconds. */ - startedAtMs?: number; + startedAtMs?: number | null; /** * Timestamp of when the job was stopped in milliseconds. */ - stoppedAtMs?: number; + stoppedAtMs?: number | null; /** * A unique identifier of the job. * **This differs from the threadId, which is created by the os and set when the thread actually starts.**. @@ -39,7 +39,7 @@ export class ThreadJob i * The id of the thread that the job is running in. * Set by the os. */ - threadId?: number; + threadId?: number | null; /** * The progress of the job in a percentage. */ @@ -47,7 +47,7 @@ export class ThreadJob i /** * The error that the job failed with. */ - error?: Error; + error?: Error | null; /** * The result that the job finished with. */ diff --git a/src/open-api/open-api-service.interface.ts b/src/open-api/open-api-service.interface.ts index 64621b5..199dd9d 100644 --- a/src/open-api/open-api-service.interface.ts +++ b/src/open-api/open-api-service.interface.ts @@ -2,6 +2,7 @@ import { ZibriApplication } from '../application'; import { OpenApiDefinition } from './open-api.model'; import { Route } from '../routing/controller-route-configuration.model'; +import { Version } from '../versioning/version.model'; /** * Interface for an open api service. @@ -14,5 +15,5 @@ export interface OpenApiServiceInterface { /** * Creates the open api definition. */ - createOpenApiDefinition: (app: ZibriApplication) => OpenApiDefinition | Promise + createOpenApiDefinition: (app: ZibriApplication, version: Version) => OpenApiDefinition | Promise } \ No newline at end of file diff --git a/src/open-api/open-api.service.ts b/src/open-api/open-api.service.ts index 01904d1..96318f1 100644 --- a/src/open-api/open-api.service.ts +++ b/src/open-api/open-api.service.ts @@ -1,4 +1,4 @@ -import swaggerUi from 'swagger-ui-express'; +import { Readable } from 'node:stream'; import { ZibriApplication } from '../application'; import { OpenApiServiceInterface } from './open-api-service.interface'; @@ -21,6 +21,7 @@ import { ManyToOnePropertyMetadata } from '../entity/models/many-to-one-property import { OneToManyPropertyMetadata } from '../entity/models/one-to-many-property-metadata.model'; import { Relation } from '../entity/models/relation.enum'; import { OmitClass } from '../entity/omit-class.model'; +import { NotFoundError } from '../error-handling/errors/not-found.error'; import { GlobalRegistry } from '../global/global-registry'; import { OnAppInit } from '../global/on-app-init.interface'; import { HttpMethod } from '../http/http-method.enum'; @@ -29,18 +30,20 @@ import { KnownHeader } from '../http/known-header.enum'; import { MimeType } from '../http/mime-type.enum'; import { type LoggerInterface } from '../logging/logger.interface'; import { FileResponse } from '../parsing/form-data/file-response.model'; +import { HtmlResponse } from '../parsing/html/html-response.model'; import { Route, ControllerRouteConfiguration } from '../routing/controller-route-configuration.model'; import { BodyMetadata } from '../routing/decorators/body.decorator'; import { ControllerData } from '../routing/decorators/controller.decorator'; import { PathParamMetadata, QueryParamMetadata, HeaderParamMetadata } from '../routing/decorators/param.decorator'; import { MissingBaseRouteError } from '../routing/missing-base-route.error'; -import { RouteHandler } from '../routing/route-configuration.model'; import { type RouterInterface } from '../routing/router.interface'; import { Newable } from '../types/newable.type'; import { FsUtilities, FsPath } from '../utilities/fs.utilities'; -import { JsonUtilities } from '../utilities/json.utilities'; import { MetadataUtilities } from '../utilities/metadata.utilities'; import { ObjectUtilities } from '../utilities/object.utilities'; +import { SupportedVersionsOptions } from '../versioning/supported-versions-options.model'; +import { Version, VersionFile } from '../versioning/version.model'; +import { type VersioningServiceInterface } from '../versioning/versioning-service.interface'; const defaultDescriptionForHttpStatus: Record = { default: 'Response', @@ -99,17 +102,37 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { @Inject(ZIBRI_DI_TOKENS.AUTH_SERVICE) private readonly authService: AuthServiceInterface, @Inject(ZIBRI_DI_TOKENS.ROUTER) - private readonly router: RouterInterface + private readonly router: RouterInterface, + @Inject(ZIBRI_DI_TOKENS.VERSIONING_SERVICE) + private readonly versioningService: VersioningServiceInterface ) { } // eslint-disable-next-line jsdoc/require-jsdoc async onAppInit(app: ZibriApplication): Promise { - const definition: OpenApiDefinition = await this.createOpenApiDefinition(app); + await this.router.registerRoute({ + httpMethod: HttpMethod.GET, + route: `${this.openApiRoute}/spec/:version`, + versions: 'all', + openApi: { useInOpenApi: false }, + pathParams: { version: { type: 'string' } }, + handler: async (req) => { + const versionValue: string = req.params['version']; + const versions: VersionFile[] = this.versioningService.getVersions(); + const version: VersionFile | undefined = versions.find(v => v.value === versionValue); + if (!version) { + throw new NotFoundError(`Version "${versionValue}" not found`); + } + return await this.createOpenApiDefinition(app, version); + } + }); + await this.logger.info(`registers the OpenAPI Explorer at ${this.openApiRoute}`); await this.router.registerRoute({ httpMethod: HttpMethod.GET, route: `${this.openApiRoute}/swagger-ui.css`, + versions: 'all', + openApi: { useInOpenApi: false }, handler: () => { const filePath: FsPath = FsUtilities.getPath(this.assetService.publicAssetsPath, 'open-api', 'swagger-ui.css'); return FileResponse.fromPath(filePath); @@ -118,6 +141,8 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { await this.router.registerRoute({ httpMethod: HttpMethod.GET, route: `${this.openApiRoute}/swagger-ui-bundle.js`, + versions: 'all', + openApi: { useInOpenApi: false }, handler: () => { const filePath: FsPath = FsUtilities.getPath(this.assetService.publicAssetsPath, 'open-api', 'swagger-ui-bundle.js'); return FileResponse.fromPath(filePath); @@ -126,6 +151,8 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { await this.router.registerRoute({ httpMethod: HttpMethod.GET, route: `${this.openApiRoute}/swagger-ui-standalone-preset.js`, + versions: 'all', + openApi: { useInOpenApi: false }, handler: () => { const filePath: FsPath = FsUtilities.getPath( this.assetService.publicAssetsPath, @@ -138,166 +165,245 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { await this.router.registerRoute({ httpMethod: HttpMethod.GET, route: `${this.openApiRoute}/swagger-ui-init.js`, - handler: (_, res) => { - res.type('.js').send([ - 'window.onload = function() {', - ' SwaggerUIBundle({', - ` spec: ${JsonUtilities.stringify(definition)},`, - ' dom_id: \'#swagger-ui\',', - ' presets: [', - ' SwaggerUIBundle.presets.apis,', - ' SwaggerUIStandalonePreset', - ' ],', - ' layout: "StandaloneLayout",', - ' requestInterceptor: (req) => {', - ' req.headers.Accept = \'application/json\'', - ` req.headers['${KnownHeader.CONTENT_TYPE}'] = \'application/json\'`, - ' return req;', - ' },', - ' defaultModelRendering: \'model\'', - ' });', - '};' - - ].join('\n')); + versions: 'all', + openApi: { useInOpenApi: false }, + handler: () => { + const versions: VersionFile[] = this.versioningService.getVersions(); + // eslint-disable-next-line jsdoc/require-jsdoc + const urls: { url: string, name: string }[] = versions.map(v => ({ + url: `${this.openApiRoute}/spec/${v.value}`, + name: v.endsAt == undefined ? `${v.value} (latest)` : v.value + })); + const latestVersion: VersionFile | undefined = versions.find(v => v.endsAt == undefined); + return FileResponse.fromStream({ + filename: 'swagger-ui-init.js', + mimeType: MimeType.JAVASCRIPT, + stream: Readable.from([ + [ + 'window.onload = function() {', + ' window.ui = SwaggerUIBundle({', + ` urls: ${JSON.stringify(urls)},`, + ` "urls.primaryName": "${latestVersion?.value ?? ''}",`, + ' dom_id: \'#swagger-ui\',', + ' presets: [', + ' SwaggerUIBundle.presets.apis,', + ' SwaggerUIStandalonePreset', + ' ],', + ' layout: "StandaloneLayout",', + ' requestInterceptor: (req) => {', + ' req.headers.Accept = \'application/json\'', + ` req.headers['${KnownHeader.CONTENT_TYPE}'] = \'application/json\'`, + ' return req;', + ' },', + ' defaultModelRendering: \'model\'', + ' });', + '};' + ].join('\n') + ]) + }); } }); await this.router.registerRoute({ httpMethod: HttpMethod.GET, route: `${this.openApiRoute}/custom.js`, - handler: (_, res) => { - res.type('.js').send([ - '(function waitForTopbar() {', - ' const topbar = document.querySelector(\'.information-container\');', - ' if (!topbar) {', - ' return setTimeout(waitForTopbar, 50);', - ' }', - ' if (document.getElementById(\'zibri-openapi-logo\')) {', - ' return;', - ' }', - '', - ' const a = document.createElement(\'a\');', - ' a.id = \'zibri-openapi-logo\';', - ' a.href = \'/\';', - '', - ' const img = document.createElement(\'img\');', - ` img.src = '${this.assetService.assetsRoute}/logo.jpg';`, - ' img.height = 100;', - ' img.width = 100;', - '', - ' a.appendChild(img);', - ` a.append('${GlobalRegistry.getAppData('name')}')`, - ' topbar.insertBefore(a, topbar.firstChild);', - '})();', - '', - '(function waitForSwagger() {', - ' if (!window.ui || typeof window.ui.specSelectors !== \'object\') {', - ' return setTimeout(waitForSwagger, 50);', - ' }', - // eslint-disable-next-line stylistic/max-len - ' const spec = window.ui.specSelectors.specJson().toJS ? window.ui.specSelectors.specJson().toJS() : window.ui.specSelectors.specJson();', - ' if (!spec || !spec.paths) {', - ' return setTimeout(waitForSwagger, 50);', - ' }', - '', - ' function normalizeMethod(m) {', - ' return String(m).toLowerCase();', - ' }', - '', - // eslint-disable-next-line cspell/spellchecker - ' const opblocks = Array.from(document.querySelectorAll(\'.opblock\'));', - // eslint-disable-next-line cspell/spellchecker - ' opblocks.forEach((op) => {', - ' try {', - // eslint-disable-next-line cspell/spellchecker - ' const methodEl = op.querySelector(\'.opblock-summary-method\');', - // eslint-disable-next-line cspell/spellchecker - ' const pathEl = op.querySelector(\'.opblock-summary-path\');', - ' if (!methodEl || !pathEl) {', - ' return;', - ' }', - ' const method = normalizeMethod(methodEl.textContent?.trim() ?? \'\');', - ' const path = (pathEl.textContent?.trim() ?? \'\');', - '', - ' const pathObj = spec.paths && spec.paths[path];', - ' if (!pathObj) {', - ' return;', - ' }', - ' const operationObj = pathObj[method];', - ' if (!operationObj) {', - ' return;', - ' }', - ' const roles = operationObj[\'x-roles\'];', - ' if (!roles || !Array.isArray(roles) || roles.length === 0) {', - ' return;', - ' }', - '', - ' if (op.querySelector(\'.zibri-roles-container\')) {', - ' return;', - ' }', - ' const container = document.createElement(\'div\');', - ' container.className = \'zibri-roles-container\';', - ' container.setAttribute(\'aria-hidden\', \'true\');', - '', - ' roles.forEach((r) => {', - ' const badge = document.createElement(\'span\');', - ' badge.className = \'zibri-role-badge\';', - ' badge.textContent = String(r);', - ' container.appendChild(badge);', - ' });', - '', - // eslint-disable-next-line cspell/spellchecker - ' const summary = op.querySelector(\'.opblock-summary-path-description-wrapper\');', - ' summary.appendChild(container);', - // ' if (summary) {', - // ' const lock = summary.querySelector(\'.authorization__btn\');', - // ' if (lock) {', - // ' summary.insertBefore(container, lock);', - // ' } else {', - // ' summary.appendChild(container);', - // ' }', - // ' }', - ' } catch (e) {', - ' console.warn(\'zibri openapi role injection failed\', e);', - ' }', - ' });', - '})();' - - ].join('\n')); + versions: 'all', + openApi: { useInOpenApi: false }, + handler: () => { + return FileResponse.fromStream({ + filename: 'custom.js', + mimeType: MimeType.JAVASCRIPT, + stream: Readable.from([ + [ + '(function() {', + ' function injectLogo(topbar) {', + ' if (topbar.querySelector(\'#zibri-openapi-logo\')) {', + ' return;', + ' }', + ' const a = document.createElement(\'a\');', + ' a.id = \'zibri-openapi-logo\';', + ' a.href = \'/\';', + '', + ' const img = document.createElement(\'img\');', + ` img.src = '${this.assetService.assetsRoute}/logo.jpg';`, + ' img.height = 100;', + ' img.width = 100;', + '', + ' a.appendChild(img);', + ` a.append('${GlobalRegistry.getAppData('name')}');`, + ' topbar.insertBefore(a, topbar.firstChild);', + ' }', + '', + ' function observe() {', + ' const topbar = document.querySelector(\'.information-container\');', + ' if (!topbar) {', + ' setTimeout(observe, 50);', + ' return;', + ' }', + ' injectLogo(topbar);', + ' new MutationObserver(() => {', + ' const t = document.querySelector(\'.information-container\');', + ' if (t) {', + ' injectLogo(t);', + ' }', + ' }).observe(document.body, { childList: true, subtree: true });', + ' }', + '', + ' observe();', + '})();', + '', + '(function() {', + ' let isInjecting = false;', + '', + ' function injectRoles() {', + ' if (isInjecting) return;', + ' if (!window.ui || typeof window.ui.specSelectors !== \'object\') return;', + '', + ' const spec = window.ui.specSelectors.specJson().toJS', + ' ? window.ui.specSelectors.specJson().toJS()', + ' : window.ui.specSelectors.specJson();', + '', + ' if (!spec || !spec.paths) return;', + '', + ' isInjecting = true;', + ' try {', + ' const normalizeMethod = (m) => String(m).toLowerCase();', + '', + // eslint-disable-next-line cspell/spellchecker + ' const opBlocks = document.querySelectorAll(\'.opblock\');', + ' opBlocks.forEach((op) => {', + ' try {', + // eslint-disable-next-line cspell/spellchecker + ' const methodEl = op.querySelector(\'.opblock-summary-method\');', + // eslint-disable-next-line cspell/spellchecker + ' const pathEl = op.querySelector(\'.opblock-summary-path\');', + ' if (!methodEl || !pathEl) return;', + '', + ' const method = normalizeMethod(methodEl.textContent.trim());', + ' const path = pathEl.textContent.trim();', + '', + ' const pathObj = spec.paths[path];', + ' if (!pathObj) return;', + '', + ' const operationObj = pathObj[method];', + ' if (!operationObj) return;', + '', + ' const roles = operationObj[\'x-roles\'];', + ' if (!roles || !Array.isArray(roles) || roles.length === 0) return;', + '', + ' if (op.querySelector(\'.zibri-roles-container\')) return;', + '', + ' const container = document.createElement(\'div\');', + ' container.className = \'zibri-roles-container\';', + ' container.setAttribute(\'aria-hidden\', \'true\');', + '', + ' roles.forEach((r) => {', + ' const badge = document.createElement(\'span\');', + ' badge.className = \'zibri-role-badge\';', + ' badge.textContent = String(r);', + ' container.appendChild(badge);', + ' });', + '', + // eslint-disable-next-line cspell/spellchecker + ' const summary = op.querySelector(\'.opblock-summary-path-description-wrapper\');', + ' if (summary) summary.appendChild(container);', + ' } catch (e) {', + ' console.warn(\'zibri openapi role injection failed\', e);', + ' }', + ' });', + ' } finally {', + ' isInjecting = false;', + ' }', + ' }', + '', + ' function waitForSwaggerAndObserve() {', + ' if (!window.ui || typeof window.ui.specSelectors !== \'object\') {', + ' return setTimeout(waitForSwaggerAndObserve, 50);', + ' }', + '', + ' const spec = window.ui.specSelectors.specJson().toJS', + ' ? window.ui.specSelectors.specJson().toJS()', + ' : window.ui.specSelectors.specJson();', + '', + ' if (!spec || !spec.paths) {', + ' return setTimeout(waitForSwaggerAndObserve, 50);', + ' }', + '', + ' // Initial injection', + ' injectRoles();', + '', + ' // Watch for version switches / re-renders', + ' const swaggerContainer = document.getElementById(\'swagger-ui\');', + ' if (!swaggerContainer) return;', + '', + ' let debounceTimer;', + ' const observer = new MutationObserver(() => {', + ' clearTimeout(debounceTimer);', + ' debounceTimer = setTimeout(() => {', + ' injectRoles();', + ' }, 200);', + ' });', + '', + ' observer.observe(swaggerContainer, { childList: true, subtree: true });', + ' }', + '', + ' waitForSwaggerAndObserve();', + '})();' + ].join('\n') + ]) + }); } }); - app.use(this.openApiRoute, swaggerUi.serve); await this.router.registerRoute({ httpMethod: HttpMethod.GET, route: this.openApiRoute, - handler: swaggerUi.setup( - definition, - { - // eslint-disable-next-line cspell/spellchecker - customfavIcon: `${this.assetService.assetsRoute}/favicon.png`, - customSiteTitle: definition.info.title, - customCssUrl: `${this.assetService.assetsRoute}/open-api/custom.css`, - customJs: `${this.openApiRoute}/custom.js` - } - ) as RouteHandler, Record, Record> + versions: 'all', + openApi: { useInOpenApi: false }, + handler: () => { + return HtmlResponse.fromString( + ` + + + + ${GlobalRegistry.getAppData('name')} | Explorer + + + + + +
+ + + + + + `, + { + csp: { + styleSrc: ['\'self\'', '\'unsafe-hashes\'', '\'sha256-RL3ie0nH+Lzz2YNqQN83mnU0J1ot4QL7b99vMdIX99w=\''], + imgSrc: ['\'self\'', 'data:'] + } + } + ); + } }); } // eslint-disable-next-line jsdoc/require-jsdoc - async createOpenApiDefinition(app: ZibriApplication): Promise { + async createOpenApiDefinition(app: ZibriApplication, version: Version): Promise { const tags: OpenApiTagObject[] = app.options.controllers.map(cls => ({ name: cls.name })); const res: OpenApiDefinition = { openapi: '3.1.0', info: { title: `${GlobalRegistry.getAppData('name')} | Explorer`, - version: GlobalRegistry.getAppData('version') ?? '0.0.0' + version: version.value }, tags, components: { securitySchemes: this.resolveSecuritySchemes() }, - paths: await this.resolveOpenApiPaths(app) + paths: await this.resolveOpenApiPaths(app, version) }; return res; } @@ -310,7 +416,8 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { return res; } - private async resolveOpenApiPaths(app: ZibriApplication): Promise { + // eslint-disable-next-line sonar/cognitive-complexity + private async resolveOpenApiPaths(app: ZibriApplication, version: Version): Promise { const res: OpenApiPaths = {}; for (const controllerClass of app.options.controllers) { @@ -321,6 +428,11 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { const routes: ControllerRouteConfiguration[] = MetadataUtilities.getControllerRoutes(controllerClass); for (const route of routes) { + const routeVersions: SupportedVersionsOptions = route.versions ?? controllerData.versions; + if (!this.versioningService.matchesVersion(routeVersions, version)) { + continue; + } + const pathParams: Record = MetadataUtilities.getRoutePathParams( controllerClass, route.controllerMethod @@ -366,6 +478,9 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { } for (const route of this.router.manuallyRegisteredRoutes.filter(r => r.openApi.useInOpenApi)) { + if (!this.versioningService.matchesVersion(route.versions, version)) { + continue; + } // Ensure an entry exists const fullPath: string = `${route.route}`.replaceAll(/:([^/]+)/g, '{$1}'); res[fullPath] ??= {}; diff --git a/src/parsing/html/csp-options.model.ts b/src/parsing/html/csp-options.model.ts index 5ce3355..a8cec9d 100644 --- a/src/parsing/html/csp-options.model.ts +++ b/src/parsing/html/csp-options.model.ts @@ -9,9 +9,12 @@ import { toKebabCase } from '../../utilities/to-kebab-case.function'; */ export type CspSource = '\'self\'' | '\'none\'' + | '\'unsafe-hashes\'' + | `\'sha256-${string}\'` | '\'unsafe-inline\'' | '\'unsafe-eval\'' | `\'nonce-${string}\'` + | 'data:' | `https://${string}` | `http://${string}`; diff --git a/src/parsing/parser.ts b/src/parsing/parser.ts index bdb3ea8..0f40342 100644 --- a/src/parsing/parser.ts +++ b/src/parsing/parser.ts @@ -93,7 +93,7 @@ export class Parser implements ParserInterface, OnAppInit { // eslint-disable-next-line jsdoc/require-jsdoc parseHeaderParam(req: HttpRequest | WebsocketRequest | HttpClientResponse, metadata: HeaderParamMetadata): unknown { - const rawValue: string | undefined = req.headers[metadata.name as KnownHeader]; + const rawValue: string | undefined = req.headers?.[metadata.name as KnownHeader]; return this.headerParamParseFunctions[metadata.type](rawValue, metadata); } @@ -112,8 +112,8 @@ export class Parser implements ParserInterface, OnAppInit { // eslint-disable-next-line jsdoc/require-jsdoc async parseBody(req: HttpRequest | WebsocketRequest | HttpClientResponse, metadata: BodyMetadata): Promise { - const contentTypeHeader: string | undefined = req.headers[KnownHeader.CONTENT_TYPE] - ?? req.headers[KnownHeader.CONTENT_TYPE.toLowerCase() as KnownHeader]; + const contentTypeHeader: string | undefined = req.headers?.[KnownHeader.CONTENT_TYPE] + ?? req.headers?.[KnownHeader.CONTENT_TYPE.toLowerCase() as KnownHeader]; let contentType: string = contentTypeHeader?.split(';')[0]?.trim().toLowerCase() ?? ''; if (!contentType.length) { if (isHttpClientResponse(req)) { diff --git a/src/plugin/invoicing/models/invoice-address.model.ts b/src/plugin/invoicing/models/invoice-address.model.ts index ddf0649..7221908 100644 --- a/src/plugin/invoicing/models/invoice-address.model.ts +++ b/src/plugin/invoicing/models/invoice-address.model.ts @@ -25,7 +25,7 @@ export class InvoiceAddress { * It is required for factur-x or x-rechnung compliant invoices. */ @Property.string({ required: false }) - email?: string; + email?: string | null; /** * The street of the address. */ @@ -61,5 +61,5 @@ export class InvoiceAddress { * The name of the company. */ @Property.string({ required: false }) - companyName?: string; + companyName?: string | null; } \ No newline at end of file diff --git a/src/plugin/invoicing/models/vat.model.ts b/src/plugin/invoicing/models/vat.model.ts index 3108241..d47493f 100644 --- a/src/plugin/invoicing/models/vat.model.ts +++ b/src/plugin/invoicing/models/vat.model.ts @@ -33,5 +33,5 @@ export class Vat { * Is required when categoryCode is set to 'E'. */ @Property.string({ required: false }) - exemptionReason?: string; + exemptionReason?: string | null; } \ No newline at end of file diff --git a/src/plugin/mailing-list/mailing-list.controller.ts b/src/plugin/mailing-list/mailing-list.controller.ts index 2f98992..8a0e052 100644 --- a/src/plugin/mailing-list/mailing-list.controller.ts +++ b/src/plugin/mailing-list/mailing-list.controller.ts @@ -22,7 +22,7 @@ import { Get } from '../../routing/decorators/get.decorator'; import { Param } from '../../routing/decorators/param.decorator'; import { Patch } from '../../routing/decorators/patch.decorator'; -@Controller('/mailing-lists', { allowOrphan: true }) +@Controller('/mailing-lists', { allowOrphan: true, versions: 'all' }) export class MailingListController implements OnAppInit { constructor( diff --git a/src/plugin/mailing-list/models/mailing-list-subscriber.model.ts b/src/plugin/mailing-list/models/mailing-list-subscriber.model.ts index 4eb0322..4811199 100644 --- a/src/plugin/mailing-list/models/mailing-list-subscriber.model.ts +++ b/src/plugin/mailing-list/models/mailing-list-subscriber.model.ts @@ -12,7 +12,7 @@ export class MailingListSubscriber extends BaseEntity { * The optional name of the subscriber. */ @Property.string({ required: false }) - name?: string; + name?: string | null; /** * The email of the subscriber. diff --git a/src/plugin/mailing-list/models/mailing-list-subscription-confirmation-token.model.ts b/src/plugin/mailing-list/models/mailing-list-subscription-confirmation-token.model.ts index 9078288..88b5097 100644 --- a/src/plugin/mailing-list/models/mailing-list-subscription-confirmation-token.model.ts +++ b/src/plugin/mailing-list/models/mailing-list-subscription-confirmation-token.model.ts @@ -30,7 +30,7 @@ export class MailingListSubscriptionConfirmationToken extends BaseEntity { * The optional name of the new subscriber that this token belongs to. */ @Property.string({ required: false }) - name?: string; + name?: string | null; /** * The id of the mailing list that this token belongs to. diff --git a/src/plugin/payment/models/payment.model.ts b/src/plugin/payment/models/payment.model.ts index e95fbf7..1254e72 100644 --- a/src/plugin/payment/models/payment.model.ts +++ b/src/plugin/payment/models/payment.model.ts @@ -45,10 +45,10 @@ export class Payment extends Ba * Additional data stored by the provider. */ @Property.unknown({ required: false }) - data?: Data; + data?: Data | null; /** * An error that the payment failed with. */ @Property.unknown({ required: false }) - error?: Error; + error?: Error | null; } \ No newline at end of file diff --git a/src/plugin/payment/providers/pay-pal/pay-pal-client.ts b/src/plugin/payment/providers/pay-pal/pay-pal-client.ts index 9153642..5b76cb7 100644 --- a/src/plugin/payment/providers/pay-pal/pay-pal-client.ts +++ b/src/plugin/payment/providers/pay-pal/pay-pal-client.ts @@ -77,7 +77,7 @@ class PayPalLink { * The method of the link. */ @Property.string({ required: false }) - method?: string; + method?: string | null; } /** @@ -93,12 +93,12 @@ class CreateOrderResp { * The status of the order. */ @Property.string({ required: false }) - status?: string; + status?: string | null; /** * The links of the order. */ @Property.array({ required: false, items: { type: 'object', cls: () => PayPalLink } }) - links?: PayPalLink[]; + links?: PayPalLink[] | null; } /** @@ -147,7 +147,7 @@ export class PayPalCapture { * The status of the capture. */ @Property.string({ required: false }) - status?: string; + status?: string | null; } /** @@ -158,7 +158,7 @@ class PayPalPayments { * The payment captures. */ @Property.array({ required: false, items: { type: 'object', cls: () => PayPalCapture } }) - captures?: PayPalCapture[]; + captures?: PayPalCapture[] | null; } /** @@ -169,7 +169,7 @@ class PayPalPurchaseUnit { * Any payments that belong to this purchase unit. */ @Property.object({ required: false, cls: () => PayPalPayments }) - payments?: PayPalPayments; + payments?: PayPalPayments | null; } /** @@ -186,13 +186,13 @@ export class CaptureOrderResp { * The status of the capture. */ @Property.string({ required: false }) - status?: string; + status?: string | null; /** * The purchase units of the capture. */ @Property.array({ required: false, items: { type: 'object', cls: () => PayPalPurchaseUnit } }) - purchase_units?: PayPalPurchaseUnit[]; + purchase_units?: PayPalPurchaseUnit[] | null; } /** @@ -209,13 +209,13 @@ class PayPalAuthorization { * The status of the authorization. */ @Property.string({ required: false }) - status?: string; + status?: string | null; /** * The amount that has been authorized. */ @Property.object({ required: false, cls: () => PaymentAmount }) - amount?: PaymentAmount; + amount?: PaymentAmount | null; } /** @@ -226,7 +226,7 @@ class PayPalPaymentsWithAuth { * The authorizations for the payments. */ @Property.array({ required: false, items: { type: 'object', cls: () => PayPalAuthorization } }) - authorizations?: PayPalAuthorization[]; + authorizations?: PayPalAuthorization[] | null; } /** @@ -237,7 +237,7 @@ class PayPalPurchaseUnitWithAuth { * The payments including the authorizations. */ @Property.object({ required: false, cls: () => PayPalPaymentsWithAuth }) - payments?: PayPalPaymentsWithAuth; + payments?: PayPalPaymentsWithAuth | null; } /** @@ -260,7 +260,7 @@ export class GetOrderResp { * The purchase units of this order. */ @Property.array({ required: false, items: { type: 'object', cls: () => PayPalPurchaseUnitWithAuth } }) - purchase_units?: PayPalPurchaseUnitWithAuth[]; + purchase_units?: PayPalPurchaseUnitWithAuth[] | null; } /** @@ -277,13 +277,13 @@ export class AuthorizationCaptureResp { * The status of the authorization. */ @Property.string({ required: false }) - status?: string; + status?: string | null; /** * Any links that belong to this authorization. */ @Property.array({ required: false, items: { type: 'object', cls: () => PayPalLink } }) - links?: PayPalLink[]; + links?: PayPalLink[] | null; } /** @@ -300,13 +300,13 @@ export class RefundCaptureResp { * The status of the refund. */ @Property.string({ required: false }) - status?: string; + status?: string | null; /** * Any links that belong to this refund. */ @Property.array({ required: false, items: { type: 'object', cls: () => PayPalLink } }) - links?: PayPalLink[]; + links?: PayPalLink[] | null; } /** diff --git a/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.test.ts b/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.test.ts index 240be39..7c2b3ed 100644 --- a/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.test.ts +++ b/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.test.ts @@ -40,7 +40,7 @@ async function getSandboxToken(clientId: string, clientSecret: string): Promise< return access_token; } -// TODO: this currently doesn't work. +// TODO: this currently doesn't work async function simulateBuyerApproval(merchantToken: string, orderId: string, returnUrl: string, cancelUrl: string): Promise { const res: Response = await fetch( `https://api-m.sandbox.paypal.com/v2/checkout/orders/${orderId}/confirm-payment-source`, diff --git a/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.ts b/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.ts index 11a7487..0f4d914 100644 --- a/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.ts +++ b/src/plugin/payment/providers/pay-pal/pay-pal.payment-provider.ts @@ -356,7 +356,7 @@ export class PayPalPaymentProvider implements PaymentProviderInterface< const resp: CaptureOrderResp = await this.client.captureOrder(payment.data.orderId); // TODO: handle payments with multiple parts const capture: PayPalCapture | undefined = resp.purchase_units?.[0]?.payments?.captures?.[0]; - const captureStatus: string | undefined = capture?.status ?? resp.status; + const captureStatus: string | undefined = capture?.status ?? resp.status ?? undefined; if (capture?.id && captureStatus === 'COMPLETED') { payment.status = PaymentStatus.PAID; diff --git a/src/preact/preact.utilities.ts b/src/preact/preact.utilities.ts index f5f2e80..4a78429 100644 --- a/src/preact/preact.utilities.ts +++ b/src/preact/preact.utilities.ts @@ -236,7 +236,7 @@ export abstract class PreactUtilities { .replaceAll('', '\\u003c/script>'); const context: HttpRequestContext | WebsocketRequestContext | undefined = inject(ZIBRI_DI_TOKENS.CURRENT_REQUEST_CONTEXT); - const nonce: string | undefined = await context?.get(ZIBRI_REQUEST_CONTEXT_TOKENS.NONCE); + const nonce: string | undefined = context?.get(ZIBRI_REQUEST_CONTEXT_TOKENS.NONCE); const nonceAttr: string = nonce ? ` nonce="${nonce}"` : ''; if (html.includes('')) { html = html.replace( diff --git a/src/preact/validate-email-templates.function.ts b/src/preact/validate-email-templates.function.ts index 6d8a4d6..5eb2c26 100644 --- a/src/preact/validate-email-templates.function.ts +++ b/src/preact/validate-email-templates.function.ts @@ -1,4 +1,4 @@ -// TODO +// TODO implement validate email templates function /** * Validates that all email templates have a valid structure. diff --git a/src/rate-limiting/rate-limiter.ts b/src/rate-limiting/rate-limiter.ts index fd902cd..8a005fd 100644 --- a/src/rate-limiting/rate-limiter.ts +++ b/src/rate-limiting/rate-limiter.ts @@ -1,3 +1,4 @@ +import { warn } from '../logging/logger.helpers'; import { Ms } from '../utilities/ms'; /** @@ -10,8 +11,13 @@ export class RateLimiter { private constructor( private readonly max: number, - private readonly intervalInMs: number + private readonly intervalInMs: number, + initialTokens: number ) { + if (initialTokens > max) { + warn('initialTokens are bigger than max, replacing initialTokens with the value of max'); + initialTokens = max; + } this.tokens = max; this.lastRefill = Date.now(); } @@ -19,47 +25,52 @@ export class RateLimiter { /** * Creates a rate limiter with the provided maximum available per day. * @param max - The maximum available per day. + * @param initialTokens - The amount of initial tokens to fill. Defaults to the value of max. * @returns The RateLimiter. */ - static perDay(max: number): RateLimiter { - return new this(max, Ms.DAY); + static perDay(max: number, initialTokens: number = max): RateLimiter { + return new this(max, Ms.DAY, initialTokens); } /** * Creates a rate limiter with the provided maximum available per hour. * @param max - The maximum available per hour. + * @param initialTokens - The amount of initial tokens to fill. Defaults to the value of max. * @returns The RateLimiter. */ - static perHour(max: number): RateLimiter { - return new this(max, Ms.HOUR); + static perHour(max: number, initialTokens: number = max): RateLimiter { + return new this(max, Ms.HOUR, initialTokens); } /** * Creates a rate limiter with the provided maximum available per minute. * @param max - The maximum available per minute. + * @param initialTokens - The amount of initial tokens to fill. Defaults to the value of max. * @returns The RateLimiter. */ - static perMinute(max: number): RateLimiter { - return new this(max, Ms.MINUTE); + static perMinute(max: number, initialTokens: number = max): RateLimiter { + return new this(max, Ms.MINUTE, initialTokens); } /** * Creates a rate limiter with the provided maximum available per second. * @param max - The maximum available per seconds. + * @param initialTokens - The amount of initial tokens to fill. Defaults to the value of max. * @returns The RateLimiter. */ - static perSecond(max: number): RateLimiter { - return new this(max, Ms.SECOND); + static perSecond(max: number, initialTokens: number = max): RateLimiter { + return new this(max, Ms.SECOND, initialTokens); } /** * Creates a rate limiter with a custom interval. * @param max - The maximum available per the given interval. * @param intervalInMs - The interval in ms. + * @param initialTokens - The amount of initial tokens to fill. Defaults to the value of max. * @returns The RateLimiter. */ - static custom(max: number, intervalInMs: number): RateLimiter { - return new this(max, intervalInMs); + static custom(max: number, intervalInMs: number, initialTokens: number = max): RateLimiter { + return new this(max, intervalInMs, initialTokens); } private refill(): void { diff --git a/src/routing/controller-route-configuration.model.ts b/src/routing/controller-route-configuration.model.ts index e8b6ee5..3d53fe3 100644 --- a/src/routing/controller-route-configuration.model.ts +++ b/src/routing/controller-route-configuration.model.ts @@ -1,4 +1,5 @@ import { HttpMethod } from '../http/http-method.enum'; +import { SupportedVersionsOptions } from '../versioning/supported-versions-options.model'; /** * Definition for a route used eg. By controllers. @@ -21,5 +22,9 @@ export type ControllerRouteConfiguration = { /** * The name of the method on the controller that is responsible for handling requests to the endpoint. */ - controllerMethod: string + controllerMethod: string, + /** + * Configuration on what versions are supported. Defaults to '^latest'. + */ + versions: SupportedVersionsOptions | undefined }; \ No newline at end of file diff --git a/src/routing/decorators/controller.decorator.ts b/src/routing/decorators/controller.decorator.ts index 6483393..cebe7c1 100644 --- a/src/routing/decorators/controller.decorator.ts +++ b/src/routing/decorators/controller.decorator.ts @@ -2,6 +2,7 @@ import { GlobalRegistry } from '../../global/global-registry'; import { Newable } from '../../types/newable.type'; import { OmitStrict } from '../../types/omit-strict.type'; import { MetadataUtilities } from '../../utilities/metadata.utilities'; +import { SupportedVersionsOptions } from '../../versioning/supported-versions-options.model'; import { Route } from '../controller-route-configuration.model'; /** @@ -12,6 +13,10 @@ export type ControllerData = { * The base route of the controller. Any endpoints inside this class will be prefixed with this. */ baseRoute: Route, + /** + * The versions that should be supported by this controller's routes. Can be overridden per route. + */ + versions: SupportedVersionsOptions, /** * Whether or not this controller is allowed to exist without being registered in the application. */ @@ -24,14 +29,15 @@ export type ControllerData = { * @param options - Additional options for the controller. */ export function Controller(baseRoute: Route, options: Partial> = {}): ClassDecorator { - const { allowOrphan = false } = options; + const { allowOrphan = false, versions = ['^latest'] } = options; return target => { // eslint-disable-next-line unicorn/error-message const stack: string = new Error().stack ?? ''; MetadataUtilities.setFilePath(target, stack); MetadataUtilities.setControllerData(target, { baseRoute, - allowOrphan + allowOrphan, + versions }); GlobalRegistry.injectables.push({ token: target as unknown as Newable, diff --git a/src/routing/decorators/create-http-decorator.function.ts b/src/routing/decorators/create-http-decorator.function.ts index 22ff32d..6835b4c 100644 --- a/src/routing/decorators/create-http-decorator.function.ts +++ b/src/routing/decorators/create-http-decorator.function.ts @@ -1,16 +1,17 @@ import { HttpMethod } from '../../http/http-method.enum'; import { MetadataUtilities } from '../../utilities/metadata.utilities'; +import { SupportedVersionsOptions } from '../../versioning/supported-versions-options.model'; import { Route, ControllerRouteConfiguration } from '../controller-route-configuration.model'; // eslint-disable-next-line jsdoc/require-jsdoc -export function createHttpDecorator(method: HttpMethod, path: Route): MethodDecorator { +export function createHttpDecorator(method: HttpMethod, path: Route, versions: SupportedVersionsOptions | undefined): MethodDecorator { return (target, propertyKey) => { const ctor: Function = target.constructor; // eslint-disable-next-line unicorn/error-message const stack: string = new Error().stack ?? ''; MetadataUtilities.setFilePath(ctor, stack); const routes: ControllerRouteConfiguration[] = MetadataUtilities.getControllerRoutes(ctor); - routes.push({ httpMethod: method, route: path, controllerMethod: propertyKey.toString() }); + routes.push({ httpMethod: method, route: path, controllerMethod: propertyKey.toString(), versions }); MetadataUtilities.setControllerRoutes(ctor, routes); }; } \ No newline at end of file diff --git a/src/routing/decorators/delete.decorator.ts b/src/routing/decorators/delete.decorator.ts index a204740..1f58971 100644 --- a/src/routing/decorators/delete.decorator.ts +++ b/src/routing/decorators/delete.decorator.ts @@ -1,11 +1,13 @@ import { HttpMethod } from '../../http/http-method.enum'; import { Route } from '../controller-route-configuration.model'; import { createHttpDecorator } from './create-http-decorator.function'; +import { BaseHttpDecoratorInput } from './http-decorator-option-input.model'; /** * Http DELETE endpoint. * @param path - The path of the endpoint, defaults to '/'. + * @param options - Additional options, like eg. The supported versions. */ -export function Delete(path: Route = '/'): MethodDecorator { - return createHttpDecorator(HttpMethod.DELETE, path); +export function Delete(path: Route = '/', options: BaseHttpDecoratorInput = {}): MethodDecorator { + return createHttpDecorator(HttpMethod.DELETE, path, options.versions); } \ No newline at end of file diff --git a/src/routing/decorators/get.decorator.ts b/src/routing/decorators/get.decorator.ts index c866827..a11d289 100644 --- a/src/routing/decorators/get.decorator.ts +++ b/src/routing/decorators/get.decorator.ts @@ -1,11 +1,13 @@ import { HttpMethod } from '../../http/http-method.enum'; import { Route } from '../controller-route-configuration.model'; import { createHttpDecorator } from './create-http-decorator.function'; +import { BaseHttpDecoratorInput } from './http-decorator-option-input.model'; /** * Http GET endpoint. * @param path - The path of the endpoint, defaults to '/'. + * @param options - Additional options, like eg. The supported versions. */ -export function Get(path: Route = '/'): MethodDecorator { - return createHttpDecorator(HttpMethod.GET, path); +export function Get(path: Route = '/', options: BaseHttpDecoratorInput = {}): MethodDecorator { + return createHttpDecorator(HttpMethod.GET, path, options.versions); } \ No newline at end of file diff --git a/src/routing/decorators/http-decorator-option-input.model.ts b/src/routing/decorators/http-decorator-option-input.model.ts new file mode 100644 index 0000000..2ba58e6 --- /dev/null +++ b/src/routing/decorators/http-decorator-option-input.model.ts @@ -0,0 +1,11 @@ +import { SupportedVersionsOptions } from '../../versioning/supported-versions-options.model'; + +/** + * Base options shared by all http decorators. + */ +export type BaseHttpDecoratorInput = { + /** + * Configuration on what versions are supported. + */ + versions?: SupportedVersionsOptions +}; \ No newline at end of file diff --git a/src/routing/decorators/patch.decorator.ts b/src/routing/decorators/patch.decorator.ts index 28d1768..d6ffa0d 100644 --- a/src/routing/decorators/patch.decorator.ts +++ b/src/routing/decorators/patch.decorator.ts @@ -1,11 +1,13 @@ import { HttpMethod } from '../../http/http-method.enum'; import { Route } from '../controller-route-configuration.model'; import { createHttpDecorator } from './create-http-decorator.function'; +import { BaseHttpDecoratorInput } from './http-decorator-option-input.model'; /** * Http PATCH endpoint. * @param path - The path of the endpoint, defaults to '/'. + * @param options - Additional options, like eg. The supported versions. */ -export function Patch(path: Route = '/'): MethodDecorator { - return createHttpDecorator(HttpMethod.PATCH, path); +export function Patch(path: Route = '/', options: BaseHttpDecoratorInput = {}): MethodDecorator { + return createHttpDecorator(HttpMethod.PATCH, path, options.versions); } \ No newline at end of file diff --git a/src/routing/decorators/post.decorator.ts b/src/routing/decorators/post.decorator.ts index 025082f..32dae94 100644 --- a/src/routing/decorators/post.decorator.ts +++ b/src/routing/decorators/post.decorator.ts @@ -1,11 +1,13 @@ import { HttpMethod } from '../../http/http-method.enum'; import { Route } from '../controller-route-configuration.model'; import { createHttpDecorator } from './create-http-decorator.function'; +import { BaseHttpDecoratorInput } from './http-decorator-option-input.model'; /** * Http POST endpoint. * @param path - The path of the endpoint, defaults to '/'. + * @param options - Additional options, like eg. The supported versions. */ -export function Post(path: Route = '/'): MethodDecorator { - return createHttpDecorator(HttpMethod.POST, path); +export function Post(path: Route = '/', options: BaseHttpDecoratorInput = {}): MethodDecorator { + return createHttpDecorator(HttpMethod.POST, path, options.versions); } \ No newline at end of file diff --git a/src/routing/resolve-route-params.function.ts b/src/routing/resolve-route-params.function.ts index a537187..7505c0d 100644 --- a/src/routing/resolve-route-params.function.ts +++ b/src/routing/resolve-route-params.function.ts @@ -87,6 +87,7 @@ async function parseRouteParams( const headerParams: Record = MetadataUtilities.getRouteHeaderParams(controllerClass, controllerMethod); for (const [indexStr, metadata] of ObjectUtilities.entries(headerParams)) { const idx: number = Number(indexStr); + context.request.headers ??= {}; context.request.headers[metadata.name as KnownHeader] = parser.parseHeaderParam(context.request, metadata) as string | undefined; params[idx] = context.request.headers[metadata.name as KnownHeader]; } diff --git a/src/routing/route-configuration.model.ts b/src/routing/route-configuration.model.ts index c6d0e78..eb8f7a6 100644 --- a/src/routing/route-configuration.model.ts +++ b/src/routing/route-configuration.model.ts @@ -16,6 +16,7 @@ import { DateParamMetadataInput, DateParamMetadata } from './models/date-param-m import { NumberParamMetadataInput, NumberParamMetadata } from './models/number-param-metadata.model'; import { ObjectParamMetadataInput, ObjectParamMetadata } from './models/object-param-metadata.model'; import { StringParamMetadataInput, StringParamMetadata } from './models/string-param-metadata.model'; +import { SupportedVersionsOptions } from '../versioning/supported-versions-options.model'; // eslint-disable-next-line jsdoc/require-jsdoc type PathMetaObjectToParamsObject> = { @@ -207,7 +208,11 @@ export type RouteConfiguration< /** * Configuration on how to handle open api. */ - openApi: OpenApiRouteConfiguration + openApi: OpenApiRouteConfiguration, + /** + * Configuration on what versions are supported. + */ + versions: SupportedVersionsOptions }; /** @@ -226,7 +231,7 @@ export type RouteConfigurationInput< QueryMetaInputObjectToMetaObject, HeaderMetaInputObjectToMetaObject >, - 'bodyMetadata' | 'pathParams' | 'queryParams' | 'headerParams' | 'openApi' + 'bodyMetadata' | 'pathParams' | 'queryParams' | 'headerParams' | 'openApi' | 'versions' > & { /** * The input metadata for the request body. @@ -247,5 +252,10 @@ export type RouteConfigurationInput< /** * Configuration on how to handle open api. */ - openApi?: Partial & Pick + openApi?: Partial & Pick, + /** + * Configuration on what versions are supported. + * Defaults to '^latest'. + */ + versions?: SupportedVersionsOptions }; \ No newline at end of file diff --git a/src/routing/router.ts b/src/routing/router.ts index c3441d8..d60f6ba 100644 --- a/src/routing/router.ts +++ b/src/routing/router.ts @@ -1,3 +1,4 @@ +import assert from 'node:assert'; import { Readable } from 'stream'; import { NextFunction, RequestHandler, Router as ExpressRouter } from 'express'; @@ -16,6 +17,7 @@ import { Inject } from '../di/decorators/inject.decorator'; import { Injectable } from '../di/decorators/injectable.decorator'; import { ZIBRI_DI_TOKENS } from '../di/default/zibri-di-tokens.default'; import { inject } from '../di/inject.function'; +import { NotFoundError } from '../error-handling/errors/not-found.error'; import { GlobalRegistry } from '../global/global-registry'; import { OnAppInit } from '../global/on-app-init.interface'; import { OnAppStart } from '../global/on-app-start.interface'; @@ -33,12 +35,22 @@ import type { ParserInterface } from '../parsing/parser.interface'; import { Newable } from '../types/newable.type'; import { MetadataUtilities } from '../utilities/metadata.utilities'; import { Ms } from '../utilities/ms'; +import { SemVerVersion } from '../utilities/sem-ver.utilities'; import type { ValidationServiceInterface } from '../validation/validation-service.interface'; import { ControllerData } from './decorators/controller.decorator'; import { AlsUtilities } from '../context/als.utilities'; import { HttpRequestContext } from '../context/request/http-request.context'; import { JsonUtilities } from '../utilities/json.utilities'; import { ObjectUtilities } from '../utilities/object.utilities'; +import { RouteWithVersionData } from '../versioning/route-with-version-data.model'; +import { SupportedVersionsOptions } from '../versioning/supported-versions-options.model'; +import { Version } from '../versioning/version.model'; +import { type VersioningServiceInterface } from '../versioning/versioning-service.interface'; + +/** + * Handler for a specific route and version. + */ +type ControllerInnerHandler = (context: HttpRequestContext, next: NextFunction) => Promise; /** * Default router implementation of Zibri. @@ -46,8 +58,9 @@ import { ObjectUtilities } from '../utilities/object.utilities'; @Injectable({ register: 'onUse' }) export class Router implements RouterInterface, OnAppInit, OnAppStart { private readonly expressRouter: ExpressRouter = ExpressRouter(); - private readonly allBaseRoutes: string[] = []; - private readonly allFinalRoutes: string[] = []; + private readonly allBaseRoutes: RouteWithVersionData[] = []; + private readonly allFinalRoutes: RouteWithVersionData[] = []; + private initComplete: boolean = false; // eslint-disable-next-line jsdoc/require-jsdoc readonly manuallyRegisteredRoutes: RouteConfiguration< BodyMetadata, @@ -55,6 +68,18 @@ export class Router implements RouterInterface, OnAppInit, OnAppStart { Record, Record >[] = []; + private readonly pendingRouteGroups: Map = new Map(); + + private get versioningService(): VersioningServiceInterface { + return inject(ZIBRI_DI_TOKENS.VERSIONING_SERVICE); + } constructor( @Inject(ZIBRI_DI_TOKENS.LOGGER) @@ -76,6 +101,13 @@ export class Router implements RouterInterface, OnAppInit, OnAppStart { await this.registerController(controller); } this.checkForOrphanedControllers(app.options.controllers); + + for (const group of this.pendingRouteGroups.values()) { + const dispatchHandler: RequestHandler = this.createDispatchHandler(group.entries); + await this.logger.debug(`- mounting ${group.httpMethod.toUpperCase()} ${group.finalRoute}`); + this.expressRouter[group.httpMethod](group.finalRoute, dispatchHandler); + } + this.initComplete = true; } // eslint-disable-next-line jsdoc/require-jsdoc @@ -107,10 +139,37 @@ export class Router implements RouterInterface, OnAppInit, OnAppStart { >( input: RouteConfigurationInput ): Promise { - if (this.allFinalRoutes.includes(`${input.httpMethod.toUpperCase()} ${input.route}`)) { - throw new Error(`The route "${input.httpMethod.toUpperCase()} ${input.route}" has been defined more than once.`); + const key: string = `${input.httpMethod.toUpperCase()} ${input.route}`; + const versions: SupportedVersionsOptions = input.versions ?? ['^latest']; + const currentLatest: SemVerVersion | undefined = GlobalRegistry.getAppData('version'); + assert(currentLatest); + + const overlappingRoute: RouteWithVersionData | undefined = this.allFinalRoutes.find( + r => r.key === key && this.versioningService.hasOverlappingVersions(r.versions, versions, currentLatest) + ); + if (overlappingRoute) { + const overlappingVersions: SupportedVersionsOptions = this.versioningService.findOverlappingVersions( + overlappingRoute.versions, + versions, + currentLatest + ); + if (overlappingVersions === 'all') { + throw new Error([ + `The route "${key}"`, + // eslint-disable-next-line sonar/no-duplicate-string + 'has been defined more than once.', + // eslint-disable-next-line sonar/no-duplicate-string + '(versions: \'all\' has been used)' + ].join(' ')); + } + + throw new Error([ + `The route "${key}"`, + `for the ${overlappingVersions.length > 1 ? 'versions' : 'version'} "${overlappingVersions.join(', ')}"`, + 'has been defined more than once.' + ].join(' ')); } - this.allFinalRoutes.push(`${input.httpMethod.toUpperCase()} ${input.route}`); + this.allFinalRoutes.push({ key, versions }); const pathParams: Record = {}; for (const key in input.pathParams) { @@ -127,6 +186,7 @@ export class Router implements RouterInterface, OnAppInit, OnAppStart { // eslint-disable-next-line typescript/no-explicit-any const route: RouteConfiguration = { + versions: ['^latest'], ...input, openApi: this.createOpenApiRouteConfiguration(input.openApi, input.httpMethod), bodyMetadata: input.bodyMetadata @@ -146,8 +206,8 @@ export class Router implements RouterInterface, OnAppInit, OnAppStart { queryParams, headerParams }; - const handler: RequestHandler = this.routeToRequestHandler(route); - await this.logger.debug(`- mounting ${route.httpMethod.toUpperCase()} ${route.route}`); + + await this.logger.debug(`- mounting ${key}`); this.manuallyRegisteredRoutes.push( route as RouteConfiguration< BodyMetadata, @@ -156,7 +216,69 @@ export class Router implements RouterInterface, OnAppInit, OnAppStart { Record > ); - this.expressRouter[route.httpMethod](route.route, handler); + + const innerHandler: ControllerInnerHandler = this.createManualRouteInnerHandler(route); + // eslint-disable-next-line typescript/typedef + const existing = this.pendingRouteGroups.get(key); + if (existing) { + existing.entries.push({ versions, innerHandler }); + } + else { + this.pendingRouteGroups.set(key, { + httpMethod: input.httpMethod, + finalRoute: input.route, + entries: [{ versions, innerHandler }] + }); + } + + // If init has already completed, mount immediately since the deferred loop has already run + if (this.initComplete) { + // eslint-disable-next-line typescript/typedef, typescript/no-non-null-assertion + const group = this.pendingRouteGroups.get(key)!; + this.expressRouter[input.httpMethod](input.route, this.createDispatchHandler(group.entries)); + } + } + + private createManualRouteInnerHandler< + BodyMetaObject extends BodyMetadata, + PathMetaObject extends Record, + QueryMetaObject extends Record, + HeaderMetaObject extends Record + >(route: RouteConfiguration): ControllerInnerHandler { + return async (context: HttpRequestContext, next: NextFunction) => { + try { + for (const key of ObjectUtilities.keys(route.pathParams)) { + (context.request.params[key] as unknown) = this.parser.parsePathParam(context.request, route.pathParams[key]); + } + for (const key of ObjectUtilities.keys(route.queryParams)) { + (context.request.query[key] as unknown) = this.parser.parseQueryParam(context.request, route.queryParams[key]); + } + for (const key of ObjectUtilities.keys(route.headerParams)) { + (context.request.headers[key] as unknown) = this.parser.parseHeaderParam(context.request, route.headerParams[key]); + } + if (route.bodyMetadata) { + context.request.body = await this.parser.parseBody(context.request, route.bodyMetadata); + } + await Promise.all([ + ...ObjectUtilities.keys(route.pathParams).map(async key => { + await this.validationService.validatePathParam(context.request.params[key], route.pathParams[key]); + }), + ...ObjectUtilities.keys(route.queryParams).map(async key => { + await this.validationService.validateQueryParam(context.request.query[key], route.queryParams[key]); + }), + ...ObjectUtilities.keys(route.headerParams).map(async key => { + await this.validationService.validateHeaderParam(context.request.headers[key], route.headerParams[key]); + }), + ...route.bodyMetadata ? [this.validationService.validateBody(context.request.body, route.bodyMetadata)] : [] + ]); + // eslint-disable-next-line typescript/no-explicit-any + const result: unknown = await route.handler(context.request as HttpRequest, context.response, next); + await this.returnResult(context.response, result, next, []); + } + catch (error) { + next(error); + } + }; } private createOpenApiRouteConfiguration( @@ -178,14 +300,14 @@ export class Router implements RouterInterface, OnAppInit, OnAppStart { switch (httpMethod) { case HttpMethod.HEAD: case HttpMethod.OPTIONS: - case HttpMethod.TRACE: - case HttpMethod.GET: { + case HttpMethod.TRACE: { return { useInOpenApi: false }; } case HttpMethod.POST: case HttpMethod.PUT: case HttpMethod.PATCH: - case HttpMethod.DELETE: { + case HttpMethod.DELETE: + case HttpMethod.GET: { return { responses: [], tags: [], @@ -195,163 +317,172 @@ export class Router implements RouterInterface, OnAppInit, OnAppStart { } } - // eslint-disable-next-line jsdoc/require-jsdoc + // eslint-disable-next-line jsdoc/require-jsdoc, sonar/cognitive-complexity async registerController(controllerClass: Newable): Promise { const controllerData: ControllerData | undefined = MetadataUtilities.getControllerData(controllerClass); if (controllerData == undefined) { throw new MissingBaseRouteError(controllerClass); } - if (this.allBaseRoutes.includes(controllerData.baseRoute)) { - throw new Error(`The base route "${controllerData.baseRoute}" has been defined on more than one controller.`); + const currentLatest: SemVerVersion | undefined = GlobalRegistry.getAppData('version'); + assert(currentLatest); + + const overlappingBaseRoute: RouteWithVersionData | undefined = this.allBaseRoutes.find( + r => r.key === controllerData.baseRoute && this.versioningService.hasOverlappingVersions( + r.versions, + controllerData.versions, + currentLatest + ) + ); + if (overlappingBaseRoute) { + const overlappingVersions: SupportedVersionsOptions = this.versioningService.findOverlappingVersions( + overlappingBaseRoute.versions, + controllerData.versions, + currentLatest + ); + if (overlappingVersions === 'all') { + throw new Error([ + `The base route "${controllerData.baseRoute}"`, + 'has been defined on more than one controller.', + '(versions: \'all\' has been used)' + ].join(' ')); + } + throw new Error([ + `The base route "${controllerData.baseRoute}"`, + `for the ${overlappingVersions.length > 1 ? 'versions' : 'version'} "${overlappingVersions.join(', ')}"`, + 'has been defined on more than one controller.' + ].join(' ')); } - this.allBaseRoutes.push(controllerData.baseRoute); + this.allBaseRoutes.push({ key: controllerData.baseRoute, versions: controllerData.versions }); const routes: ControllerRouteConfiguration[] = MetadataUtilities.getControllerRoutes(controllerClass); for (const route of routes) { - const handler: RequestHandler = await this.controllerRouteToRequestHandler(controllerClass, route); const finalRoute: string = controllerData.baseRoute === '/' ? route.route : `${controllerData.baseRoute}${route.route}`; - if (this.allFinalRoutes.includes(`${route.httpMethod.toUpperCase()} ${finalRoute}`)) { - throw new Error( - `The route "${route.httpMethod.toUpperCase()} ${finalRoute}" has been defined more than once.`, - { cause: controllerClass } + const key: string = `${route.httpMethod.toUpperCase()} ${finalRoute}`; + const versions: SupportedVersionsOptions = route.versions ?? controllerData.versions; + + const overlappingRoute: RouteWithVersionData | undefined = this.allFinalRoutes.find( + r => r.key === key && this.versioningService.hasOverlappingVersions(r.versions, versions, currentLatest) + ); + if (overlappingRoute) { + const overlappingVersions: SupportedVersionsOptions = this.versioningService.findOverlappingVersions( + overlappingRoute.versions, + versions, + currentLatest ); + if (overlappingVersions === 'all') { + throw new Error([ + `The route "${key}"`, + 'has been defined more than once.', + '(versions: \'all\' has been used)' + ].join(' ')); + } + + throw new Error([ + `The route "${key}"`, + `for the ${overlappingVersions.length > 1 ? 'versions' : 'version'} "${overlappingVersions.join(', ')}"`, + 'has been defined more than once.' + ].join(' ')); + } + + this.allFinalRoutes.push({ key, versions }); + + const innerHandler: ControllerInnerHandler = await this.createControllerInnerHandler(controllerClass, route); + // eslint-disable-next-line typescript/typedef + const existing = this.pendingRouteGroups.get(key); + if (existing) { + existing.entries.push({ versions, innerHandler }); + continue; } - this.allFinalRoutes.push(`${route.httpMethod.toUpperCase()} ${finalRoute}`); - await this.logger.debug(`- mounting ${route.httpMethod.toUpperCase()} ${finalRoute}`); - this.expressRouter[route.httpMethod](finalRoute, handler); + + this.pendingRouteGroups.set(key, { + httpMethod: route.httpMethod, + finalRoute, + entries: [{ versions, innerHandler }] + }); } } - private routeToRequestHandler< - BodyMetaObject extends BodyMetadata, - PathMetaObject extends Record, - QueryMetaObject extends Record, - HeaderMetaObject extends Record - >(route: RouteConfiguration): RequestHandler { - const handler: RequestHandler = (async (request: HttpRequest, res, next) => { - Object.defineProperty(request, 'params', { - value: { ...request.params }, - writable: true, - configurable: true, - enumerable: true - }); - Object.defineProperty(request, 'query', { - value: { ...request.query }, - writable: true, - configurable: true, - enumerable: true - }); - Object.defineProperty(request, 'headers', { - value: { ...request.headers }, - writable: true, - configurable: true, - enumerable: true - }); + private createDispatchHandler( + // eslint-disable-next-line jsdoc/require-jsdoc + entries: { versions: SupportedVersionsOptions, innerHandler: ControllerInnerHandler }[] + ): RequestHandler { + return (async (request: HttpRequest, res, next) => { + Object.defineProperty( + request, + 'params', + { + value: { ...request.params }, + writable: true, + configurable: true, + enumerable: true + } + ); + Object.defineProperty( + request, + 'query', + { + value: { ...request.query }, + writable: true, + configurable: true, + enumerable: true + } + ); + Object.defineProperty( + request, + 'headers', + { + value: { ...request.headers }, + writable: true, + configurable: true, + enumerable: true + } + ); const context: HttpRequestContext = new HttpRequestContext(request, res, undefined, undefined); await AlsUtilities.runWithHttpRequestContext(context, async () => { try { - // parse - for (const key of ObjectUtilities.keys(route.pathParams)) { - (context.request.params[key] as unknown) = this.parser.parsePathParam(context.request, route.pathParams[key]); - } - for (const key of ObjectUtilities.keys(route.queryParams)) { - (context.request.query[key] as unknown) = this.parser.parseQueryParam( - context.request, - route.queryParams[key] - ); - } - for (const key of ObjectUtilities.keys(route.headerParams)) { - (context.request.headers[key] as unknown) = this.parser.parseHeaderParam( - context.request, - route.headerParams[key] - ); + const version: Version = await this.versioningService.resolveVersion(context); + // eslint-disable-next-line typescript/typedef + const match = entries.find(e => this.versioningService.matchesVersion(e.versions, version)); + if (!match) { + throw new NotFoundError(`Could not find route "${request.url}" for version "${version.value}"`); } - if (route.bodyMetadata) { - context.request.body = await this.parser.parseBody(context.request, route.bodyMetadata); - } - // validate - await Promise.all([ - ...ObjectUtilities.keys(route.pathParams).map(async key => { - await this.validationService.validatePathParam(context.request.params[key], route.pathParams[key]); - }), - ...ObjectUtilities.keys(route.queryParams).map(async key => { - await this.validationService.validateQueryParam(context.request.query[key], route.queryParams[key]); - }), - ...ObjectUtilities.keys(route.headerParams).map(async key => { - await this.validationService.validateHeaderParam(context.request.headers[key], route.headerParams[key]); - }), - ...route.bodyMetadata ? [this.validationService.validateBody(context.request.body, route.bodyMetadata)] : [] - ]); - - // eslint-disable-next-line typescript/no-explicit-any - const result: unknown = await route.handler(context.request as HttpRequest, context.response, next); - await this.returnResult(context.response, result, next, []); + await match.innerHandler(context, next); } catch (error) { next(error); } }); }) as RequestHandler; - return handler; } - private async controllerRouteToRequestHandler( + private async createControllerInnerHandler( controllerClass: Newable, route: ControllerRouteConfiguration - ): Promise { + ): Promise { const responses: OpenApiResponse[] = MetadataUtilities.getRouteResponses(controllerClass, route.controllerMethod); if (!responses.length) { await this.logger.warn(`No responses defined on route ${controllerClass.name}.${route.controllerMethod}`); } - const handler: RequestHandler = (async (request: HttpRequest, res, next) => { - Object.defineProperty(request, 'params', { - value: { ...request.params }, - writable: true, - configurable: true, - enumerable: true - }); - Object.defineProperty(request, 'query', { - value: { ...request.query }, - writable: true, - configurable: true, - enumerable: true - }); - Object.defineProperty(request, 'headers', { - value: { ...request.headers }, - writable: true, - configurable: true, - enumerable: true - }); - - const context: HttpRequestContext = new HttpRequestContext( - request, - res, - controllerClass, - route.controllerMethod - ); - await AlsUtilities.runWithHttpRequestContext(context, async () => { - try { - await this.authService.checkAccess(controllerClass, route.controllerMethod, context); - const controller: unknown = inject(controllerClass); - const params: unknown[] = await resolveRouteParams( - controllerClass, - route.controllerMethod, - // eslint-disable-next-line typescript/no-unsafe-member-access, typescript/no-explicit-any - ((controller as any)[route.controllerMethod] as Function).length, - context - ); - - // eslint-disable-next-line typescript/no-unsafe-call, typescript/no-explicit-any, typescript/no-unsafe-member-access - const result: unknown = await ((controller as any)[route.controllerMethod] as Function)(...params); - await this.returnResult(context.response, result, next, responses); - } - catch (error) { - next(error); - } - }); - }) as RequestHandler; - return handler; + return async (context: HttpRequestContext, next: NextFunction) => { + try { + await this.authService.checkAccess(controllerClass, route.controllerMethod, context); + const controller: unknown = inject(controllerClass); + const params: unknown[] = await resolveRouteParams( + controllerClass, + route.controllerMethod, + // eslint-disable-next-line typescript/no-unsafe-member-access, typescript/no-explicit-any + ((controller as any)[route.controllerMethod] as Function).length, + context + ); + // eslint-disable-next-line typescript/no-unsafe-call, typescript/no-explicit-any, typescript/no-unsafe-member-access + const result: unknown = await ((controller as any)[route.controllerMethod] as Function)(...params); + await this.returnResult(context.response, result, next, responses); + } + catch (error) { + next(error); + } + }; } private async returnResult(res: HttpResponse, result: unknown, next: NextFunction, responses: OpenApiResponse[]): Promise { diff --git a/src/types/version.type.ts b/src/types/version.type.ts deleted file mode 100644 index f7e972f..0000000 --- a/src/types/version.type.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * Definition for a SemVer version. - */ -export type Version = `${number}.${number}.${number}`; \ No newline at end of file diff --git a/src/utilities/compare-versions.function.ts b/src/utilities/compare-versions.function.ts deleted file mode 100644 index a3e91aa..0000000 --- a/src/utilities/compare-versions.function.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Version } from '../types/version.type'; - -/** - * Compares the given versions and check if the first one is bigger, equal or smaller than the second one. - * @param v1 - The first version. - * @param v2 - The second version to compare against. - * @returns 'bigger', 'equal' or 'smaller'. - */ -export function compareVersion(v1: Version, v2: Version): 'bigger' | 'equal' | 'smaller' { - const [v1One, v1Two, v1Three] = v1; - const [v2One, v2Two, v2Three] = v2; - if (v1One > v2One) { - return 'bigger'; - } - if (v1Two > v2Two) { - return 'bigger'; - } - if (v1Three > v2Three) { - return 'bigger'; - } - - if (v2One > v1One) { - return 'smaller'; - } - if (v2Two > v1Two) { - return 'smaller'; - } - if (v2Three > v1Three) { - return 'smaller'; - } - - return 'equal'; -} \ No newline at end of file diff --git a/src/utilities/fs.utilities.ts b/src/utilities/fs.utilities.ts index 6ac04ed..d4f0b6b 100644 --- a/src/utilities/fs.utilities.ts +++ b/src/utilities/fs.utilities.ts @@ -173,7 +173,7 @@ export abstract class FsUtilities { * Creates a file at the given path. * @param p - The path of the new file to create. * @param data - The data to write into the file. Can be a raw data string or an array of lines, which are joined by \n. - * @param recursive - Whether or not to recursively create the file. + * @param recursive - Whether or not to recursively create the file. Defaults to true. */ static async createFile(p: FsPath, data: string | string[], recursive: boolean = true): Promise { if (await this.exists(p)) { diff --git a/src/utilities/is-version.function.ts b/src/utilities/is-version.function.ts deleted file mode 100644 index 02f98dd..0000000 --- a/src/utilities/is-version.function.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { isNumeric } from './is-numeric.function'; -import { Version } from '../types/version.type'; - -// eslint-disable-next-line jsdoc/require-returns -/** - * Checks whether the given value is a valid SemVer Version. - * @param value - The value to check. - */ -export function isVersion(value: string): value is Version { - const parts: string[] = value.split('.'); - if (parts.length !== 3) { - return false; - } - const [one, two, three] = parts; - return isNumeric(one) && isNumeric(two) && isNumeric(three); -} \ No newline at end of file diff --git a/src/utilities/sem-ver.utilities.ts b/src/utilities/sem-ver.utilities.ts new file mode 100644 index 0000000..8f48001 --- /dev/null +++ b/src/utilities/sem-ver.utilities.ts @@ -0,0 +1,270 @@ +import { isNumeric } from './is-numeric.function'; +import { SemVerMatcher } from '../versioning/supported-versions-options.model'; + +/** + * Definition for a SemVer version. + */ +export type SemVerVersion = `${number}.${number}.${number}`; + +/** + * Semver tuple consisting of major, minor and patch. + */ +type SemVerTuple = readonly [number, number, number]; + +/** + * A sem ver range, consisting of a min and max version . + */ +type SemVerRangeBounds = { + /** + * The minimum sem ver version. + */ + min: SemVerTuple, + /** + * The maximum sem ver version. + */ + max: SemVerTuple +}; + +/** + * Encapsulates functionalities around handling semver versions. + */ +export abstract class SemVerUtilities { + /** + * Checks whether the given value is a valid SemVer version. + * @param value - The value to check. + * @returns True if the value is in the format "number.number.number", false otherwise. + */ + static isSemVerVersion(value: string): value is SemVerVersion { + const parts: string[] = value.split('.'); + if (parts.length !== 3) { + return false; + } + + return parts.every(isNumeric); + } + + /** + * Compares the given versions and checks if the first one is bigger, equal or smaller than the second one. + * @param v1 - The first version. + * @param v2 - The second version to compare against. + * @returns 'bigger', 'equal' or 'smaller'. + */ + static compare(v1: SemVerVersion, v2: SemVerVersion): 'bigger' | 'equal' | 'smaller' { + const result: number = this.compareTuple(this.parseStrict(v1), this.parseStrict(v2)); + if (result > 0) { + return 'bigger'; + } + if (result < 0) { + return 'smaller'; + } + return 'equal'; + } + + /** + * Checks if the given semver matcher overlaps with one of the provided matchers. + * @param version - The version/matcher to check. + * @param matchers - The matchers to check against. + * @returns True if the version/matcher overlaps with at least one matcher, false otherwise. + */ + static matches(version: SemVerMatcher, matchers: SemVerMatcher[]): boolean { + const versionRange: SemVerRangeBounds = this.toRange(version); + return matchers.some((matcher: SemVerMatcher) => this.intersects(versionRange, this.toRange(matcher))); + } + + private static toRange(matcher: SemVerMatcher): SemVerRangeBounds { + const parts: string[] = matcher.trim().split(/\s+/) + .filter(Boolean); + + return parts.reduce( + (acc: SemVerRangeBounds, part: string) => this.intersect(acc, this.atomicRange(part)), + this.fullRange() + ); + } + + private static atomicRange(matcher: string): SemVerRangeBounds { + if (matcher.startsWith('^')) { + return this.caretRange(matcher.slice(1)); + } + + if (matcher.startsWith('~')) { + return this.tildeRange(matcher.slice(1)); + } + + if (matcher.startsWith('>=')) { + return { + min: this.parseStrict(matcher.slice(2)), + max: this.infinity() + }; + } + + if (matcher.startsWith('>')) { + return { + min: this.increment(this.parseStrict(matcher.slice(1))), + max: this.infinity() + }; + } + + if (matcher.startsWith('<=')) { + return { + min: this.zero(), + max: this.increment(this.parseStrict(matcher.slice(2))) + }; + } + + if (matcher.startsWith('<')) { + return { + min: this.zero(), + max: this.parseStrict(matcher.slice(1)) + }; + } + + const exact: SemVerTuple = this.parseStrict(matcher); + return { + min: exact, + max: this.increment(exact) + }; + } + + private static caretRange(raw: string): SemVerRangeBounds { + const [major, minor, patch] = this.parseLoose(raw); + const min: SemVerTuple = [major, minor, patch]; + + if (major > 0) { + return { + min, + max: [major + 1, 0, 0] + }; + } + + if (minor > 0) { + return { + min, + max: [0, minor + 1, 0] + }; + } + + return { + min, + max: [0, 0, patch + 1] + }; + } + + private static tildeRange(raw: string): SemVerRangeBounds { + const parts: string[] = raw.split('.'); + + if (parts.length < 1 || parts.length > 3) { + throw new Error(`Invalid semver matcher: ~${raw}`); + } + + const [major, minor, patch] = this.parseLoose(raw); + const min: SemVerTuple = [major, minor, patch]; + + if (parts.length >= 2) { + return { + min, + max: [major, minor + 1, 0] + }; + } + + return { + min, + max: [major + 1, 0, 0] + }; + } + + private static parseStrict(version: string): SemVerTuple { + if (!this.isSemVerVersion(version)) { + throw new Error(`Invalid semver version: ${version}`); + } + + const [major, minor, patch] = version.split('.').map(Number); + return [major, minor, patch]; + } + + private static parseLoose(version: string): SemVerTuple { + const parts: string[] = version.split('.'); + + if (parts.length < 1 || parts.length > 3) { + throw new Error(`Invalid semver matcher: ${version}`); + } + + const [majorRaw, minorRaw, patchRaw] = parts; + + if ( + !isNumeric(majorRaw) + || (minorRaw !== undefined && !isNumeric(minorRaw)) + || (patchRaw !== undefined && !isNumeric(patchRaw)) + ) { + throw new Error(`Invalid semver matcher: ${version}`); + } + + return [ + Number(majorRaw), + Number(minorRaw ?? 0), + Number(patchRaw ?? 0) + ]; + } + + private static compareTuple(a: SemVerTuple, b: SemVerTuple): number { + if (a[0] > b[0]) { + return 1; + } + if (a[0] < b[0]) { + return -1; + } + + if (a[1] > b[1]) { + return 1; + } + if (a[1] < b[1]) { + return -1; + } + + if (a[2] > b[2]) { + return 1; + } + if (a[2] < b[2]) { + return -1; + } + + return 0; + } + + private static intersect(a: SemVerRangeBounds, b: SemVerRangeBounds): SemVerRangeBounds { + return { + min: this.maxTuple(a.min, b.min), + max: this.minTuple(a.max, b.max) + }; + } + + private static intersects(a: SemVerRangeBounds, b: SemVerRangeBounds): boolean { + return this.compareTuple(this.maxTuple(a.min, b.min), this.minTuple(a.max, b.max)) < 0; + } + + private static minTuple(a: SemVerTuple, b: SemVerTuple): SemVerTuple { + return this.compareTuple(a, b) <= 0 ? a : b; + } + + private static maxTuple(a: SemVerTuple, b: SemVerTuple): SemVerTuple { + return this.compareTuple(a, b) >= 0 ? a : b; + } + + private static increment([major, minor, patch]: SemVerTuple): SemVerTuple { + return [major, minor, patch + 1]; + } + + private static zero(): SemVerTuple { + return [0, 0, 0]; + } + + private static infinity(): SemVerTuple { + return [Number.POSITIVE_INFINITY, 0, 0]; + } + + private static fullRange(): SemVerRangeBounds { + return { + min: this.zero(), + max: this.infinity() + }; + } +} \ No newline at end of file diff --git a/src/utilities/uuid.utilities.ts b/src/utilities/uuid.utilities.ts index 1f01e85..99c48fb 100644 --- a/src/utilities/uuid.utilities.ts +++ b/src/utilities/uuid.utilities.ts @@ -1,4 +1,4 @@ -import { v4 } from 'uuid'; +import { randomUUID } from 'node:crypto'; /** * Utilities for dealing with uuid. @@ -9,6 +9,6 @@ export abstract class UUIDUtilities { * @returns A v4 uuid string. */ static generate(): string { - return v4(); + return randomUUID(); } } \ No newline at end of file diff --git a/src/versioning/route-with-version-data.model.ts b/src/versioning/route-with-version-data.model.ts new file mode 100644 index 0000000..e624040 --- /dev/null +++ b/src/versioning/route-with-version-data.model.ts @@ -0,0 +1,15 @@ +import { SupportedVersionsOptions } from './supported-versions-options.model'; + +/** + * Defines a route identified by a key with their respective versions. + */ +export type RouteWithVersionData = { + /** + * The supported versions of the route. + */ + versions: SupportedVersionsOptions, + /** + * The key that identifies the route. + */ + key: string +}; \ No newline at end of file diff --git a/src/versioning/supported-versions-options.model.ts b/src/versioning/supported-versions-options.model.ts new file mode 100644 index 0000000..1db3173 --- /dev/null +++ b/src/versioning/supported-versions-options.model.ts @@ -0,0 +1,25 @@ +import { ExcludeStrict } from '../types/exclude-strict.type'; +import { SemVerVersion } from '../utilities/sem-ver.utilities'; + +// eslint-disable-next-line jsdoc/require-jsdoc +type SemVerRange = `^${number}` | `^${number}.${number}` | `^${SemVerVersion}` + | `~${number}` | `~${number}.${number}` | `~${SemVerVersion}` + | `>=${SemVerVersion}` + | `>${SemVerVersion}` + | `<=${SemVerVersion}` + | `<${SemVerVersion}`; + +/** + * Matcher for a version. + */ +export type VersionMatcher = 'latest' | '^latest' | '~latest' | SemVerVersion | SemVerRange | `${SemVerRange} ${SemVerRange}`; + +/** + * Matcher for a semver version. + */ +export type SemVerMatcher = ExcludeStrict; + +/** + * The options available to define what versions are supported by eg. A route. + */ +export type SupportedVersionsOptions = 'all' | (VersionMatcher)[]; \ No newline at end of file diff --git a/src/versioning/version.model.ts b/src/versioning/version.model.ts new file mode 100644 index 0000000..0b96764 --- /dev/null +++ b/src/versioning/version.model.ts @@ -0,0 +1,30 @@ +import { RouteWithVersionData } from './route-with-version-data.model'; +import { SemVerVersion } from '../utilities/sem-ver.utilities'; + +/** + * A version, consisting of the actual version value and a starts end end date. + */ +export type Version = { + /** + * The actual version value in the SemVer format. + */ + value: SemVerVersion, + /** + * The date after which this should be the active version. Used for date based version resolution. + */ + startsAt: Date, + /** + * The end date of this version. Can be left open if the version is the current latest. + */ + endsAt?: Date | null +}; + +/** + * Data stored inside of a version file. + */ +export type VersionFile = Version & { + /** + * The routes at the time of the version bump. + */ + routes: RouteWithVersionData[] +}; \ No newline at end of file diff --git a/src/versioning/versioning-service.interface.ts b/src/versioning/versioning-service.interface.ts new file mode 100644 index 0000000..d54ea6d --- /dev/null +++ b/src/versioning/versioning-service.interface.ts @@ -0,0 +1,39 @@ +import { SupportedVersionsOptions } from './supported-versions-options.model'; +import { Version, VersionFile } from './version.model'; +import { HttpRequestContext } from '../context/request/http-request.context'; +import { WebsocketRequestContext } from '../context/request/websocket-request.context'; +import { SemVerVersion } from '../utilities/sem-ver.utilities'; + +/** + * Interface for a versioning service. + */ +export interface VersioningServiceInterface { + /** + * Resolves the version from the given request context. + */ + resolveVersion: (context: HttpRequestContext | WebsocketRequestContext) => Version | Promise, + /** + * Checks if any of the given version options matches the resolved version. + */ + matchesVersion: (versions: SupportedVersionsOptions, resolvedVersion: Version) => boolean, + /** + * Finds overlapping versions between two options. + */ + findOverlappingVersions: ( + a: SupportedVersionsOptions, + b: SupportedVersionsOptions, + currentLatest: SemVerVersion + ) => SupportedVersionsOptions, + /** + * Checks whether or not the given options have some overlap. + */ + hasOverlappingVersions: ( + a: SupportedVersionsOptions, + b: SupportedVersionsOptions, + currentLatest: SemVerVersion + ) => boolean, + /** + * Returns all versions created in the file system. + */ + getVersions: () => VersionFile[] +} \ No newline at end of file diff --git a/src/versioning/versioning-websocket.test.ts b/src/versioning/versioning-websocket.test.ts new file mode 100644 index 0000000..fc09283 --- /dev/null +++ b/src/versioning/versioning-websocket.test.ts @@ -0,0 +1,351 @@ +import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; +import { io, Socket } from 'socket.io-client'; + +import { VersionFile } from './version.model'; +import { type VersioningServiceInterface } from './versioning-service.interface'; +import { VersioningService } from './versioning.service'; +import { testFileFolder } from '../__testing__/constants'; +import { defaultTestServerProviders } from '../__testing__/test-server/providers'; +import { startTestServer, StartedTestServer } from '../__testing__/test-server/start-test-server.function'; +import { HttpRequestContext } from '../context/request/http-request.context'; +import { WebsocketRequestContext } from '../context/request/websocket-request.context'; +import { Inject } from '../di/decorators/inject.decorator'; +import { ZIBRI_DI_TOKENS } from '../di/default/zibri-di-tokens.default'; +import { inject } from '../di/inject.function'; +import { type Header } from '../http/header.type'; +import { type RouterInterface } from '../routing/router.interface'; +import { Newable } from '../types/newable.type'; +import { FsPath, FsUtilities } from '../utilities/fs.utilities'; +import { JsonUtilities } from '../utilities/json.utilities'; +import { SemVerVersion } from '../utilities/sem-ver.utilities'; +import { WebsocketController } from '../websocket/decorators/websocket-controller.decorator'; +import { WebsocketRoute } from '../websocket/decorators/websocket-route.decorator'; +import { WebsocketEvent } from '../websocket/models/websocket-event.enum'; +import { WebsocketMessage } from '../websocket/models/websocket-message.model'; + +const testVersionsDir: FsPath = FsUtilities.getPath(testFileFolder, 'versions-ws'); + +class TestVersioningService extends VersioningService { + constructor( + @Inject(ZIBRI_DI_TOKENS.VERSION_HEADER) + versionHeader: Header, + @Inject(ZIBRI_DI_TOKENS.ROUTER) + router: RouterInterface + ) { + super(versionHeader, router); + // eslint-disable-next-line typescript/no-unsafe-member-access, typescript/no-explicit-any + (this as any).versionsPath = testVersionsDir; + } +} + +@WebsocketController({ allowOrphan: true, versions: 'all' }) +class VersionResolveController { + constructor( + @Inject(ZIBRI_DI_TOKENS.VERSIONING_SERVICE) + private readonly versioningService: VersioningServiceInterface + ) {} + + @WebsocketRoute('resolve-version') + async resolveVersion(): Promise { + const context: HttpRequestContext | WebsocketRequestContext | undefined = inject(ZIBRI_DI_TOKENS.CURRENT_REQUEST_CONTEXT); + if (!context) { + throw new Error('context missing'); + } + return this.versioningService.resolveVersion(context); + } +} + +@WebsocketController({ allowOrphan: true, eventPrefix: 'dispatch:' }) +class VersionedDispatchController { + @WebsocketRoute('event', { versions: ['1.0.0'] }) + handleV1(): { handler: string } { + return { handler: 'v1' }; + } + + @WebsocketRoute('event', { versions: ['2.0.0'] }) + handleV2(): { handler: string } { + return { handler: 'v2' }; + } + + @WebsocketRoute('latest-only', { versions: ['latest'] }) + handleLatest(): { handler: string } { + return { handler: 'latest' }; + } + + @WebsocketRoute('only-all', { versions: 'all' }) + handleAll(): { handler: string } { + return { handler: 'all' }; + } + + @WebsocketRoute('only-v1', { versions: ['1.0.0'] }) + handleOnlyV1(): { handler: string } { + return { handler: 'v1' }; + } +} + +let server: StartedTestServer; +let baseUrl: string; + +async function writeVersionFile(file: VersionFile): Promise { + const filePath: FsPath = FsUtilities.getPath(testVersionsDir, `${file.value}.json`); + await FsUtilities.mkdir(testVersionsDir); + await FsUtilities.upsertFile(filePath, JsonUtilities.stringify(file)); +} + +async function restartServer(options: { version?: SemVerVersion, websocketControllers?: Newable[] } = {}): Promise { + await server.reInit(options); + baseUrl = await server.start(); +} + +async function sendWsEvent( + url: string, + event: string, + payload: unknown = {}, + versionHeader?: string +): Promise { + return new Promise((resolve, reject) => { + const socket: Socket = io(url, { + auth: { offset: 0 }, + extraHeaders: versionHeader ? { 'x-version': versionHeader } : {} + }); + + const timeout: NodeJS.Timeout = setTimeout(() => { + socket.disconnect(); + reject(new Error('Timed out waiting for websocket response')); + }, 5000); + + socket.once(WebsocketEvent.RESPONSE, (msg: WebsocketMessage, ack?: () => void) => { + clearTimeout(timeout); + ack?.(); + socket.disconnect(); + resolve(msg); + }); + + socket.once('connect', () => { + socket.emit(event, payload); + }); + + socket.once('connect_error', err => { + clearTimeout(timeout); + reject(err); + }); + }); +} + +async function expectWsConnectError( + url: string, + versionHeader: string +): Promise { + return new Promise((resolve, reject) => { + const socket: Socket = io(url, { + auth: { offset: 0 }, + extraHeaders: { 'x-version': versionHeader } + }); + + socket.once('connect', () => { + socket.disconnect(); + reject(new Error('Expected connect_error but socket connected')); + }); + + socket.once('connect_error', err => { + socket.disconnect(); + resolve(err); + }); + }); +} + +beforeAll(async () => { + await FsUtilities.rm(testVersionsDir); + server = await startTestServer({ + version: '1.0.0', + providers: [ + ...defaultTestServerProviders, + { token: ZIBRI_DI_TOKENS.VERSIONING_SERVICE, useClass: TestVersioningService } + ], + websocketControllers: [VersionResolveController, VersionedDispatchController] + }); + baseUrl = await server.start(); +}, 15000); + +afterAll(async () => { + await server.shutdown(); +}); + +describe('VersioningService websocket integration', () => { + describe('resolveVersion via websocket', () => { + beforeEach(async () => { + await FsUtilities.rm(testVersionsDir); + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date('2024-01-01'), + endsAt: new Date('2024-02-01'), + routes: [] + }); + await writeVersionFile({ + value: '2.0.0', + startsAt: new Date('2024-02-01'), + routes: [] + }); + await writeVersionFile({ + value: '3.0.0', + startsAt: new Date('2024-01-01'), + endsAt: new Date('2024-01-15'), + routes: [] + }); + await restartServer({ version: '2.0.0', websocketControllers: [VersionResolveController, VersionedDispatchController] }); + }); + + it('resolves latest version when no header provided', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'resolve-version'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.value).toBe('2.0.0'); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.endsAt).toBeUndefined(); + }); + + it('resolves specific version by header', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'resolve-version', {}, '1.0.0'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.value).toBe('1.0.0'); + }); + + it('returns error for unknown version', async () => { + const err: Error = await expectWsConnectError(baseUrl, '9.9.9'); + expect(err.message).toContain('Version "9.9.9" not found'); + }); + }); + + describe('Version-based event dispatch', () => { + beforeEach(async () => { + await FsUtilities.rm(testVersionsDir); + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date('2024-01-01'), + endsAt: new Date('2024-02-01'), + routes: [] + }); + await writeVersionFile({ + value: '2.0.0', + startsAt: new Date('2024-02-01'), + routes: [] + }); + await writeVersionFile({ + value: '3.0.0', + startsAt: new Date('2024-01-01'), + endsAt: new Date('2024-01-15'), + routes: [] + }); + await restartServer({ version: '2.0.0', websocketControllers: [VersionedDispatchController] }); + }); + + // --- dispatch:event --- + it('uses latest handler when no version header (active version)', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:latest-only'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.handler).toBe('latest'); + }); + + it('uses latest handler when explicitly requesting active version 2.0.0', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:latest-only', {}, '2.0.0'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.handler).toBe('latest'); + }); + + it('returns 404 when requesting older version on a latest-only route', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:latest-only', {}, '1.0.0'); + expect(msg.ok).toBe(false); + expect(msg.status).toBe(404); + }); + + it('uses v1 handler when requesting 1.0.0 explicitly', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:event', {}, '1.0.0'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.handler).toBe('v1'); + }); + + it('uses v2 handler when requesting 2.0.0 explicitly', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:event', {}, '2.0.0'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.handler).toBe('v2'); + }); + + it('returns not found error for version with no handler', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:event', {}, '3.0.0'); + expect(msg.ok).toBe(false); + expect(msg.status).toBe(404); + }); + + it('returns 400 error for unknown version', async () => { + const err: Error = await expectWsConnectError(baseUrl, '4.0.0'); + expect(err.message).toContain('Version "4.0.0" not found'); + }); + + // --- dispatch:only-all --- + it('uses all handler for any valid version', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:only-all', {}, '1.0.0'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.handler).toBe('all'); + }); + + it('uses all handler when no version header', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:only-all'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.handler).toBe('all'); + }); + + // --- dispatch:only-v1 --- + it('returns not found for active version on v1-only event', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:only-v1'); + expect(msg.ok).toBe(false); + expect(msg.status).toBe(404); + }); + + it('serves v1 handler when requesting 1.0.0 on v1-only event', async () => { + const msg: WebsocketMessage = await sendWsEvent(baseUrl, 'dispatch:only-v1', {}, '1.0.0'); + expect(msg.ok).toBe(true); + // eslint-disable-next-line typescript/no-explicit-any, typescript/no-unsafe-member-access + expect((msg as any).data.handler).toBe('v1'); + }); + }); + + describe('Validation: overlap between latest and active version', () => { + @WebsocketController({ allowOrphan: true, eventPrefix: 'overlap:' }) + class OverlapWebsocketController { + @WebsocketRoute('event', { versions: ['latest'] }) + latest(): { handler: string } { + return { handler: 'latest' }; + } + + @WebsocketRoute('event', { versions: ['2.0.0'] }) + v2(): { handler: string } { + return { handler: 'v2' }; + } + } + + beforeEach(async () => { + await FsUtilities.rm(testVersionsDir); + await writeVersionFile({ + value: '2.0.0', + startsAt: new Date('2024-02-01'), + routes: [] + }); + }); + + it('throws if latest overlaps with the current active version', async () => { + await expect( + restartServer({ + version: '2.0.0', + websocketControllers: [OverlapWebsocketController] + }) + ).rejects.toThrow(/defined more than once/); + }); + }); +}); \ No newline at end of file diff --git a/src/versioning/versioning.service.test.ts b/src/versioning/versioning.service.test.ts new file mode 100644 index 0000000..1e2a624 --- /dev/null +++ b/src/versioning/versioning.service.test.ts @@ -0,0 +1,582 @@ +import assert from 'node:assert'; +import { Dirent } from 'node:fs'; + +import { afterAll, beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; + +import { RouteWithVersionData } from './route-with-version-data.model'; +import { Version, VersionFile } from './version.model'; +import { type VersioningServiceInterface } from './versioning-service.interface'; +import { VersioningService } from './versioning.service'; +import { testFileFolder } from '../__testing__/constants'; +import { defaultTestServerProviders } from '../__testing__/test-server/providers'; +import { startTestServer, StartedTestServer } from '../__testing__/test-server/start-test-server.function'; +import { HttpRequestContext } from '../context/request/http-request.context'; +import { WebsocketRequestContext } from '../context/request/websocket-request.context'; +import { Inject } from '../di/decorators/inject.decorator'; +import { ZIBRI_DI_TOKENS } from '../di/default/zibri-di-tokens.default'; +import { inject } from '../di/inject.function'; +import { type Header } from '../http/header.type'; +import { Controller } from '../routing/decorators/controller.decorator'; +import { Get } from '../routing/decorators/get.decorator'; +import { type RouterInterface } from '../routing/router.interface'; +import { Newable } from '../types/newable.type'; +import { FsPath, FsUtilities } from '../utilities/fs.utilities'; +import { JsonUtilities } from '../utilities/json.utilities'; +import { SemVerVersion } from '../utilities/sem-ver.utilities'; + +const testVersionsDir: FsPath = FsUtilities.getPath(testFileFolder, 'versions'); + +// --------------------------------------------------------------- +// Custom versioning service that writes to a temporary directory +// --------------------------------------------------------------- +class TestVersioningService extends VersioningService { + constructor( + @Inject(ZIBRI_DI_TOKENS.VERSION_HEADER) + versionHeader: Header, + @Inject(ZIBRI_DI_TOKENS.ROUTER) + router: RouterInterface + ) { + super(versionHeader, router); + // eslint-disable-next-line typescript/no-unsafe-member-access, typescript/no-explicit-any + (this as any).versionsPath = testVersionsDir; + } +} + +// --------------------------------------------------------------- +// Test controller that exposes the versioning service via HTTP +// --------------------------------------------------------------- +@Controller('/version-test', { allowOrphan: true, versions: 'all' }) +class VersionTestController { + constructor( + @Inject(ZIBRI_DI_TOKENS.VERSIONING_SERVICE) + private readonly versioningService: VersioningServiceInterface + ) {} + + @Get('/resolve') + async resolveVersion(): Promise { + const context: HttpRequestContext | WebsocketRequestContext | undefined = inject(ZIBRI_DI_TOKENS.CURRENT_REQUEST_CONTEXT); + if (!context) { + throw new Error('context missing'); + } + return await this.versioningService.resolveVersion(context); + } +} + +// --------------------------------------------------------------- +// Test controller with versioned routes for validation tests +// --------------------------------------------------------------- +@Controller('/validated', { versions: ['1.0.0'], allowOrphan: true }) +class ValidatedController { + @Get('/endpoint') + getEndpoint(): { ok: boolean } { + return { ok: true }; + } +} + +// Controller with a route that only uses 'latest', not '1.0.0' +@Controller('/latest-test', { versions: ['latest'], allowOrphan: true }) +class LatestOnlyController { + @Get('/endpoint') + get(): {} { + return {}; + } +} + +@Controller('/bad', { versions: ['2.0.0'], allowOrphan: true }) +class BadController { + @Get('/endpoint') + get(): {} { + return {}; + } +} + +let server: StartedTestServer; +let baseUrl: string; + +// Helper to write a version file into the temp dir +async function writeVersionFile(file: VersionFile): Promise { + const filePath: FsPath = FsUtilities.getPath(testVersionsDir, `${file.value}.json`); + await FsUtilities.mkdir(testVersionsDir); + await FsUtilities.upsertFile(filePath, JsonUtilities.stringify(file)); +} + +// Helper to read a version file +async function readVersionFile(version: string): Promise { + const filePath: FsPath = FsUtilities.getPath(testVersionsDir, `${version}.json`); + const raw: string = await FsUtilities.readFile(filePath); + return JsonUtilities.parse(raw); +} + +// Reset state before each test: clear temp dir and stop/restart? +// Since version files are read only during afterAppInit, which runs once at startup, +// we need to restart the server for each test scenario. We'll use a helper. +async function restartServer(options: { version?: SemVerVersion, controllers?: Newable[] } = {}): Promise { + await server.reInit(options); + baseUrl = await server.start(); +} + +beforeAll(async () => { + await FsUtilities.rm(testVersionsDir); + // Start the server with our custom versioning service and a test controller + server = await startTestServer({ + version: '1.0.0', // default version, we'll change per test + providers: [ + ...defaultTestServerProviders, + { token: ZIBRI_DI_TOKENS.VERSIONING_SERVICE, useClass: TestVersioningService } + ], + controllers: [VersionTestController, ValidatedController] + }); + baseUrl = await server.start(); +}, 15000); + +afterAll(async () => { + await server.shutdown(); +}); + +describe('VersioningService integration', () => { + describe('First startup', () => { + it('creates the initial version file with routes', async () => { + const file: VersionFile = await readVersionFile('1.0.0'); + expect(file.value).toBe('1.0.0'); + expect(file.endsAt).toBeUndefined(); + expect(file.routes.length).toBeGreaterThan(0); + + const validatedRoute: RouteWithVersionData | undefined = file.routes.find(r => r.key === 'GET /validated/endpoint'); + expect(validatedRoute).toBeDefined(); + expect(validatedRoute?.versions).toEqual(['1.0.0']); + }); + }); + + describe('Already at latest version (no-op)', () => { + it('does not modify the version file', async () => { + await restartServer({ version: '1.0.0' }); + const file: VersionFile = await readVersionFile('1.0.0'); + expect(file.endsAt).toBeUndefined(); + // No new files created + const files: Dirent[] = await FsUtilities.readdir(testVersionsDir); + expect(files.map(f => f.name)).toEqual(['1.0.0.json']); + }); + }); + + describe('Normal version bump', () => { + const oldVersion: SemVerVersion = '1.0.0'; + const newVersion: SemVerVersion = '2.0.0'; + + beforeEach(async () => { + await FsUtilities.rm(testVersionsDir); + await restartServer({ + version: oldVersion, + controllers: [ValidatedController] + }); + await restartServer({ + version: newVersion, + controllers: [ValidatedController] + }); + }); + + it('deprecates the old version and creates the new one', async () => { + const oldFile: VersionFile = await readVersionFile(oldVersion); + assert(oldFile.endsAt); + const transitionTime: Date = new Date(oldFile.endsAt); + + const newFile: VersionFile = await readVersionFile(newVersion); + expect(newFile.value).toBe(newVersion); + expect(newFile.endsAt).toBeUndefined(); + expect(new Date(newFile.startsAt).getTime()).toBe(transitionTime.getTime()); + // Routes in new file reflect current in-code routes (e.g., still include the validated endpoint) + expect(newFile.routes).toEqual(expect.arrayContaining([expect.objectContaining({ key: 'GET /validated/endpoint' })])); + }); + }); + + describe('Crash recovery: zero active versions', () => { + const oldVersion: SemVerVersion = '1.0.0'; + const newVersion: SemVerVersion = '2.0.0'; + + beforeEach(async () => { + // Old version already deprecated (crash scenario) + await writeVersionFile({ + value: oldVersion, + startsAt: new Date('2024-01-01'), + endsAt: new Date('2024-02-01'), + routes: [{ key: 'GET /validated/endpoint', versions: [oldVersion] }] + }); + // New version does NOT exist + await restartServer({ version: newVersion }); + }); + + it('leaves the old file untouched and creates the new one with a fresh startsAt', async () => { + const oldFile: VersionFile = await readVersionFile(oldVersion); + expect(oldFile.endsAt).toBe('2024-02-01T00:00:00.000Z'); + + const newFile: VersionFile = await readVersionFile(newVersion); + expect(newFile.value).toBe(newVersion); + expect(newFile.endsAt).toBeUndefined(); + assert(oldFile.endsAt); + expect(new Date(newFile.startsAt).getTime()).toBeGreaterThan(new Date(oldFile.endsAt).getTime()); + // Validation still passed: the route GET /validated/endpoint still exists, so no error. + }); + }); + + describe('Crash recovery: multiple active versions (incomplete bump)', () => { + const v1: SemVerVersion = '1.0.0'; + const v2: SemVerVersion = '2.0.0'; // new app version, file already created prematurely + + beforeEach(async () => { + await writeVersionFile({ + value: v1, + startsAt: new Date('2024-01-01'), + routes: [{ key: 'GET /validated/endpoint', versions: [v1] }] + }); + // Prematurely created new version file with stale routes + await writeVersionFile({ + value: v2, + startsAt: new Date('2024-02-01'), + routes: [] // will be updated + }); + await restartServer({ version: v2, controllers: [ValidatedController] }); + }); + + it('chains endsAt of old versions and updates new file routes', async () => { + const oldFile: VersionFile = await readVersionFile(v1); + expect(oldFile.endsAt).toBe('2024-02-01T00:00:00.000Z'); + + const newFile: VersionFile = await readVersionFile(v2); + expect(newFile.endsAt).toBeUndefined(); + expect(newFile.routes).toEqual(expect.arrayContaining([expect.objectContaining({ key: 'GET /validated/endpoint' })])); + }); + }); + + describe('Validation: route removed', () => { + it('throws if a previously supported route is missing in the new version', async () => { + // Prepare old active version with an extra route + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date(), + routes: [ + { key: 'GET /validated/endpoint', versions: ['1.0.0'] }, + { key: 'POST /delete-me', versions: ['1.0.0'] } + ] + }); + + // Start with version 2.0.0 and a controller that does NOT define POST /delete-me + // The ValidatedController only has GET /validated/endpoint, so POST /delete-me is missing. + await expect( + restartServer({ version: '2.0.0', controllers: [VersionTestController, ValidatedController] }) + ).rejects.toThrow(/Route "POST \/delete-me" no longer exists in code/); + }); + }); + + describe('Validation: unknown version in stored route (file-level first)', () => { + // Controller that supports the version referenced in the old snapshot + @Controller('/validated', { versions: ['2.0.0'], allowOrphan: true }) + class ValidatedForV2Controller { + @Get('/endpoint') + getEndpoint(): { ok: boolean } { + return { ok: true }; + } + } + + it('throws file-level error before checking in-code routes', async () => { + await FsUtilities.rm(testVersionsDir); + // Create a version file with a stored route referencing a version that doesn't exist + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date(), + routes: [{ key: 'GET /validated/endpoint', versions: ['2.0.0'] }] + }); + + await expect( + restartServer({ version: '3.0.0', controllers: [ValidatedForV2Controller] }) + ).rejects.toThrow( + 'Route version mismatch detected:\n- Unknown version "2.0.0" in version file "1.0.0.json" on route "GET /validated/endpoint"' + ); + }); + }); + + describe('Validation: unknown version in in-code route', () => { + it('throws after file-level checks pass if in-code route references unknown version', async () => { + await FsUtilities.rm(testVersionsDir); + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date(), + routes: [{ key: 'GET /validated/endpoint', versions: ['1.0.0'] }] + }); + + await expect( + restartServer({ version: '1.0.1', controllers: [ValidatedController, BadController] }) + ).rejects.toThrow(/Unknown version "2.0.0" on route "GET \/bad\/endpoint"/); + }); + + it('does NOT throw after file-level checks pass if in-code route references new version', async () => { + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date(), + routes: [{ key: 'GET /validated/endpoint', versions: ['1.0.0'] }] + }); + + await expect( + restartServer({ version: '2.0.0', controllers: [ValidatedController, BadController] }) + ).resolves.not.toThrow(); + }); + }); + + describe('Validation: latest route must support previous version', () => { + it('throws if route uses "latest" but does not explicitly support the previous version', async () => { + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date(), + routes: [] // empty snapshot + }); + + await expect( + restartServer({ version: '2.0.0', controllers: [VersionTestController, LatestOnlyController] }) + ).rejects.toThrow( + /There are routes that have been used under the previous version "1.0.0"/ + ); + }); + }); + + describe('resolveVersion via HTTP', () => { + beforeEach(async () => { + // Ensure we have version files for 1.0.0 and 2.0.0 active + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date('2024-01-01'), + endsAt: new Date('2024-02-01'), + routes: [] + }); + await writeVersionFile({ + value: '2.0.0', + startsAt: new Date('2024-02-01'), + routes: [] + }); + await restartServer({ version: '2.0.0' }); + }); + + it('returns latest version when no header provided', async () => { + const res: Response = await fetch(`${baseUrl}/version-test/resolve`); + const body: Version = await res.json() as Version; + expect(res.status).toBe(200); + expect(body.value).toBe('2.0.0'); + expect(body.endsAt).toBeUndefined(); + }); + + it('returns specific version by string', async () => { + const res: Response = await fetch(`${baseUrl}/version-test/resolve`, { + headers: { 'x-version': '1.0.0' } + }); + const body: Version = await res.json() as Version; + expect(res.status).toBe(200); + expect(body.value).toBe('1.0.0'); + }); + + it('resolves version by date (boundary case)', async () => { + const res: Response = await fetch(`${baseUrl}/version-test/resolve`, { + headers: { 'x-version': '2024-02-01T00:00:00.000Z' } + }); + const body: Version = await res.json() as Version; + expect(res.status).toBe(200); + expect(body.value).toBe('2.0.0'); // half-open: startsAt <= date and endsAt > date or null + }); + + it('returns 400 for unknown version string', async () => { + const res: Response = await fetch(`${baseUrl}/version-test/resolve`, { + headers: { 'x-version': '9.9.9' } + }); + expect(res.status).toBe(400); + }); + + it('returns 400 for date with no active version', async () => { + const res: Response = await fetch(`${baseUrl}/version-test/resolve`, { + headers: { 'x-version': '2023-12-31T00:00:00.000Z' } + }); + expect(res.status).toBe(400); + }); + }); + + describe('Version-based routing', () => { + @Controller('/routing-test', { allowOrphan: true }) + class VersionedRouteController { + @Get('/multi', { versions: ['1.0.0'] }) + multiV1(): object { + return { handler: 'v1' }; + } + + @Get('/multi', { versions: ['2.0.0'] }) + multiV2(): object { + return { handler: 'v2' }; + } + + @Get('/missing-v2', { versions: ['1.0.0'] }) + missingV2Handler(): object { + return { handler: 'v1' }; + } + + @Get('/only-latest', { versions: ['latest'] }) + onlyLatest(): object { + return { handler: 'latest' }; + } + + @Get('/only-all', { versions: 'all' }) + onlyAll(): object { + return { handler: 'all' }; + } + } + + beforeEach(async () => { + await writeVersionFile({ + value: '1.0.0', + startsAt: new Date('2024-01-01'), + endsAt: new Date('2024-02-01'), + routes: [] + }); + await writeVersionFile({ + value: '2.0.0', + startsAt: new Date('2024-02-01'), + routes: [] + }); + await writeVersionFile({ + value: '3.0.0', + startsAt: new Date('2024-01-01'), + endsAt: new Date('2024-01-15'), + routes: [] + }); + + await restartServer({ + version: '2.0.0', + controllers: [VersionedRouteController] + }); + }); + + // --- /multi --- + it('uses v2 handler when no header (active version)', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/multi`); + expect(res.status).toBe(200); + // eslint-disable-next-line typescript/no-unsafe-assignment + const body: { handler: string } = await res.json(); + expect(body.handler).toBe('v2'); + }); + + it('uses v1 handler when requesting 1.0.0 explicitly', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/multi`, { + headers: { 'x-version': '1.0.0' } + }); + expect(res.status).toBe(200); + // eslint-disable-next-line typescript/no-unsafe-assignment + const body: { handler: string } = await res.json(); + expect(body.handler).toBe('v1'); + }); + + it('uses v2 handler when requesting 2.0.0 explicitly', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/multi`, { + headers: { 'x-version': '2.0.0' } + }); + expect(res.status).toBe(200); + // eslint-disable-next-line typescript/no-unsafe-assignment + const body: { handler: string } = await res.json(); + expect(body.handler).toBe('v2'); + }); + + it('falls back to "all" handler for known version 3.0.0 without specific handler', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/multi`, { + headers: { 'x-version': '3.0.0' } + }); + expect(res.status).toBe(404); + }); + + it('returns 400 for unknown version', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/multi`, { + headers: { 'x-version': '4.0.0' } + }); + expect(res.status).toBe(400); + }); + + // --- /missing-v2 --- + it('returns 404 when active version has no handler', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/missing-v2`); + expect(res.status).toBe(404); + }); + + it('serves v1 handler when requesting 1.0.0 even if no 2.0.0 handler exists', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/missing-v2`, { + headers: { 'x-version': '1.0.0' } + }); + expect(res.status).toBe(200); + // eslint-disable-next-line typescript/no-unsafe-assignment + const body: { handler: string } = await res.json(); + expect(body.handler).toBe('v1'); + }); + + it('returns 404 when explicit 2.0.0 has no handler', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/missing-v2`, { + headers: { 'x-version': '2.0.0' } + }); + expect(res.status).toBe(404); + }); + + // --- /only-latest --- + it('uses latest handler for active version (no header)', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/only-latest`); + expect(res.status).toBe(200); + // eslint-disable-next-line typescript/no-unsafe-assignment + const body: { handler: string } = await res.json(); + expect(body.handler).toBe('latest'); + }); + + it('uses latest handler when explicitly requesting active version 2.0.0', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/only-latest`, { + headers: { 'x-version': '2.0.0' } + }); + expect(res.status).toBe(200); + // eslint-disable-next-line typescript/no-unsafe-assignment + const body: { handler: string } = await res.json(); + expect(body.handler).toBe('latest'); + }); + + it('returns 404 when requesting older version on a latest‑only route', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/only-latest`, { + headers: { 'x-version': '1.0.0' } + }); + expect(res.status).toBe(404); + }); + + // --- /only-all --- + it('uses all handler for any valid version (1.0.0)', async () => { + const res: Response = await fetch(`${baseUrl}/routing-test/only-all`, { + headers: { 'x-version': '1.0.0' } + }); + expect(res.status).toBe(200); + // eslint-disable-next-line typescript/no-unsafe-assignment + const body: { handler: string } = await res.json(); + expect(body.handler).toBe('all'); + }); + }); + + describe('Validation: overlap between latest and active version', () => { + @Controller('/overlap', { allowOrphan: true }) + class OverlapController { + @Get('/endpoint', { versions: ['latest'] }) + latest(): { handler: string } { + return { handler: 'latest' }; + } + + @Get('/endpoint', { versions: ['2.0.0'] }) + v2(): { handler: string } { + return { handler: 'v2' }; + } + } + + it('throws if latest overlaps with the current active version', async () => { + await FsUtilities.rm(testVersionsDir); + await writeVersionFile({ + value: '2.0.0', + startsAt: new Date('2024-02-01'), + routes: [] + }); + + await expect( + restartServer({ version: '2.0.0', controllers: [OverlapController] }) + ).rejects.toThrow(/defined more than once/); + }); + }); +}); \ No newline at end of file diff --git a/src/versioning/versioning.service.ts b/src/versioning/versioning.service.ts new file mode 100644 index 0000000..b1bfe01 --- /dev/null +++ b/src/versioning/versioning.service.ts @@ -0,0 +1,496 @@ +import { Dirent } from 'node:fs'; + +import { Version, VersionFile } from './version.model'; +import { VersioningServiceInterface } from './versioning-service.interface'; +import { ZibriApplication } from '../application'; +import { RouteWithVersionData } from './route-with-version-data.model'; +import { SemVerMatcher, SupportedVersionsOptions, VersionMatcher } from './supported-versions-options.model'; +import { HttpRequestContext } from '../context/request/http-request.context'; +import { WebsocketRequestContext } from '../context/request/websocket-request.context'; +import { Inject } from '../di/decorators/inject.decorator'; +import { Injectable } from '../di/decorators/injectable.decorator'; +import { ZIBRI_DI_TOKENS } from '../di/default/zibri-di-tokens.default'; +import { BadRequestError } from '../error-handling/errors/bad-request.error'; +import { AfterAppInit } from '../global/after-app-init.interface'; +import { type Header } from '../http/header.type'; +import { ControllerRouteConfiguration } from '../routing/controller-route-configuration.model'; +import { ControllerData } from '../routing/decorators/controller.decorator'; +import { type RouterInterface } from '../routing/router.interface'; +import { OmitStrict } from '../types/omit-strict.type'; +import { FsPath, FsUtilities } from '../utilities/fs.utilities'; +import { isDate } from '../utilities/is-date.function'; +import { JsonUtilities } from '../utilities/json.utilities'; +import { MetadataUtilities } from '../utilities/metadata.utilities'; +import { SemVerUtilities, SemVerVersion } from '../utilities/sem-ver.utilities'; +import { WebsocketControllerData } from '../websocket/decorators/websocket-controller.decorator'; +import { WebsocketControllerRouteConfiguration } from '../websocket/models/websocket-controller-route-configuration.model'; + +const ROUTE_VERSION_MISMATCH_ERROR_MESSAGE: string = 'Route version mismatch detected:'; + +/** + * Default implementation of the versioning service. + */ +@Injectable({ register: 'onUse' }) +export class VersioningService implements VersioningServiceInterface, AfterAppInit { + + private readonly versionsPath: FsPath = FsUtilities.getPath(__dirname, '..', 'versions'); + private versionFiles: VersionFile[] = []; + + constructor( + @Inject(ZIBRI_DI_TOKENS.VERSION_HEADER) + private readonly versionHeader: Header, + @Inject(ZIBRI_DI_TOKENS.ROUTER) + private readonly router: RouterInterface + ) {} + + // eslint-disable-next-line jsdoc/require-jsdoc + async afterAppInit(app: ZibriApplication): Promise { + await FsUtilities.mkdir(this.versionsPath); + this.versionFiles = await this.loadAllVersionFiles(); + const currentLatestVersions: VersionFile[] = this.versionFiles.filter(v => v.endsAt == undefined); + + if (currentLatestVersions.length === 1 && currentLatestVersions.at(0)?.value === app.options.version) { + // is already at the latest version, doesn't need to do anything. + return; + } + + if (!this.versionFiles.length) { + await this.createVersionFile({ + startsAt: new Date(), + value: app.options.version, + routes: this.getAllRoutes(app) + }); + this.versionFiles = await this.loadAllVersionFiles(); + return; + } + + const currentLatest: VersionFile | undefined = this.resolveCurrentLatest(currentLatestVersions, app); + const inCodeRoutes: RouteWithVersionData[] = this.getAllRoutes(app); + const storedRoutes: RouteWithVersionData[] = currentLatest ? currentLatest.routes : []; + + this.validateNoDowngrade(app); + this.validateRouteVersionSnapshot(inCodeRoutes, storedRoutes, this.versionFiles, currentLatest?.value, app.options.version); + this.validateRouteVersionsExist(inCodeRoutes, this.versionFiles, app.options.version); + this.validateLatestRoutes(inCodeRoutes, app.options.version, this.versionFiles); + + // Create new version file (deprecate the old one) + const transitionTime: Date = new Date(); + const newVersion: VersionFile | undefined = this.versionFiles.find(v => v.value === app.options.version); + + if (newVersion != undefined) { + // Sort old active versions (excluding the new one) by startsAt + const oldActives: VersionFile[] = currentLatestVersions + .filter(v => v.value !== app.options.version) + .sort((a, b) => new Date(a.startsAt).getTime() - new Date(b.startsAt).getTime()); + + // Chain the end dates: each old version ends when the next one starts + for (let i: number = 0; i < oldActives.length; i++) { + const nextStart: Date = i < oldActives.length - 1 + ? new Date(oldActives[i + 1].startsAt) + : new Date(newVersion.startsAt); + await this.updateVersionFile(oldActives[i], { endsAt: nextStart }); + } + + // Ensure the new file’s routes are up to date + await this.updateVersionFile(newVersion, { routes: inCodeRoutes }); + this.versionFiles = await this.loadAllVersionFiles(); + return; + } + + await this.createVersionFile({ + value: app.options.version, + startsAt: transitionTime, + routes: inCodeRoutes + }); + + for (const active of currentLatestVersions) { + await this.updateVersionFile(active, { endsAt: transitionTime }); + } + this.versionFiles = await this.loadAllVersionFiles(); + } + + private validateNoDowngrade(app: ZibriApplication): void { + const latestVersion: VersionFile = this.versionFiles.reduce( + (max, v) => SemVerUtilities.compare(v.value, max.value) === 'bigger' ? v : max + ); + if (SemVerUtilities.compare(latestVersion.value, app.options.version) === 'bigger') { + throw new Error( + [ + `Version "${app.options.version}" was previously active but has been superseded by "${latestVersion.value}".`, + 'Downgrading is not supported automatically. To resolve this, either:', + '- Revert your code and version files together via git', + `- Manually delete and update version files so that "${app.options.version}" is treated as a fresh version` + ].join('\n') + ); + } + } + + private resolveCurrentLatest(currentLatestVersions: VersionFile[], app: ZibriApplication): VersionFile | undefined { + if (currentLatestVersions.length === 1) { + return currentLatestVersions.at(0); + } + if (currentLatestVersions.length < 1) { + return this.versionFiles.reduce((max, v) => SemVerUtilities.compare(v.value, max.value) === 'bigger' ? v : max); + } + + const currentLatest: VersionFile | undefined = currentLatestVersions.find(v => v.value !== app.options.version); + if (!currentLatest) { + throw new Error( + 'Inconsistent version state: multiple active versions exist, but they all represent the global version.' + + 'Check for duplicate version files.' + ); + } + return currentLatest; + } + + private async loadAllVersionFiles(): Promise { + const entries: Dirent[] = await FsUtilities.readdir(FsUtilities.getPath(this.versionsPath)); + const res: (VersionFile | undefined)[] = await Promise.all(entries.map(async e => { + if (!e.isFile() || !e.name.endsWith('.json')) { + return undefined; + } + const raw: string = await FsUtilities.readFile(FsUtilities.getPath(e.parentPath, e.name)); + return JsonUtilities.parse(raw); + })); + return res.filter(Boolean) as VersionFile[]; + } + + private async createVersionFile(data: VersionFile): Promise { + await FsUtilities.createFile( + FsUtilities.getPath(this.versionsPath, `${data.value}.json`), + JsonUtilities.stringify(data) + ); + } + + private async updateVersionFile(versionFile: VersionFile, data: Partial>): Promise { + const updated: VersionFile = { ...versionFile, ...data }; + await FsUtilities.updateFile( + FsUtilities.getPath(this.versionsPath, `${versionFile.value}.json`), + JsonUtilities.stringify(updated), + 'replace' + ); + } + + private validateRouteVersionSnapshot( + inCodeRoutes: RouteWithVersionData[], + storedRoutes: RouteWithVersionData[], + versionFiles: VersionFile[], + currentLatest: SemVerVersion | undefined, + newVersion: SemVerVersion + ): void { + if (!currentLatest) { + return; + } + const knownVersions: Set = new Set(versionFiles.map(e => e.value)); + const errors: string[] = []; + + for (const storedRoute of storedRoutes) { + const routes: RouteWithVersionData[] = inCodeRoutes.filter(r => r.key === storedRoute.key); + + if (!routes.length) { + errors.push(`Route "${storedRoute.key}" no longer exists in code.`); + continue; + } + + const removedVersions: SemVerVersion[] = this.getRemovedVersions( + storedRoute.versions, + routes.some(r => r.versions === 'all') + ? 'all' + : routes.flatMap(r => r.versions === 'all' ? [] : r.versions), + [...knownVersions], + currentLatest, + newVersion + + ); + if (!removedVersions.length) { + continue; + } + + const versionLabel: string = removedVersions.length > 1 ? 'versions' : 'version'; + errors.push( + `Route "${storedRoute.key}" removed ${versionLabel}: ${removedVersions.join(', ')}` + ); + } + + if (errors.length) { + throw new Error([ROUTE_VERSION_MISMATCH_ERROR_MESSAGE, ...errors.map(error => `- ${error}`)].join('\n')); + } + } + + private getRemovedVersions( + previous: SupportedVersionsOptions, + current: SupportedVersionsOptions, + knownVersions: SemVerVersion[], + currentLatest: SemVerVersion, + newVersion: SemVerVersion + ): SemVerVersion[] { + const previousResolved: SemVerVersion[] = this.resolveConcreteVersions(previous, knownVersions, currentLatest); + const currentResolved: SemVerVersion[] = this.resolveConcreteVersions(current, knownVersions, newVersion); + + return previousResolved.filter(v => !currentResolved.includes(v)); + } + + private resolveConcreteVersions( + versions: SupportedVersionsOptions, + knownVersions: SemVerVersion[], + currentLatest: SemVerVersion + ): SemVerVersion[] { + if (versions === 'all') { + return [...knownVersions]; + } + + const resolvedMatchers: SemVerMatcher[] = versions.map( + v => this.versionMatcherToSemVerMatcher(v, currentLatest) + ); + + return knownVersions.filter(version => SemVerUtilities.matches(version, resolvedMatchers)); + } + + private validateRouteVersionsExist( + inCodeRoutes: RouteWithVersionData[], + versionFiles: VersionFile[], + newVersion: SemVerVersion + ): void { + const knownVersions: SemVerVersion[] = versionFiles.map(v => v.value); + const candidateVersions: SemVerVersion[] = [...knownVersions, newVersion]; + const errors: string[] = []; + + // eslint-disable-next-line typescript/typedef + const validateRoute = (route: RouteWithVersionData, latest: SemVerVersion, location: string): void => { + if (route.versions === 'all') { + return; + } + + for (const matcher of route.versions) { + const resolvedMatcher: SemVerMatcher = this.versionMatcherToSemVerMatcher(matcher, latest); + + const matchesAny: boolean = candidateVersions.some(version => SemVerUtilities.matches(version, [resolvedMatcher])); + + if (!matchesAny) { + errors.push(`Unknown version "${matcher}" ${location}`); + } + } + }; + + for (const file of versionFiles) { + for (const route of file.routes) { + validateRoute(route, file.value, `in version file "${file.value}.json" on route "${route.key}"`); + } + } + + if (errors.length) { + throw new Error([ROUTE_VERSION_MISMATCH_ERROR_MESSAGE, ...errors.map(error => `- ${error}`)].join('\n')); + } + + for (const route of inCodeRoutes) { + validateRoute(route, newVersion, `on route "${route.key}"`); + } + + if (errors.length) { + throw new Error([ROUTE_VERSION_MISMATCH_ERROR_MESSAGE, ...errors.map(error => `- ${error}`)].join('\n')); + } + } + + private validateLatestRoutes( + inCodeRoutes: RouteWithVersionData[], + currentVersion: SemVerVersion, + allVersionFiles: VersionFile[] + ): void { + const previousCandidates: VersionFile[] = allVersionFiles.filter( + v => v.endsAt == undefined && v.value !== currentVersion + ); + if (previousCandidates.length === 0) { + return; + } + + const previousLatest: VersionFile = previousCandidates.reduce((max, v) => new Date(v.startsAt) > new Date(max.startsAt) ? v : max); + const previousRoutesByKey: Map = new Map( + previousLatest.routes.map(route => [route.key, route]) + ); + + const routesThatNeedUpdate: RouteWithVersionData[] = inCodeRoutes.filter(r => { + if (r.versions === 'all') { + return false; + } + + const usesLatestAlias: boolean = r.versions.some( + v => v === 'latest' || v === '^latest' || v === '~latest' + ); + + if (!usesLatestAlias) { + return false; + } + + const previousRoute: RouteWithVersionData | undefined = previousRoutesByKey.get(r.key); + if (!previousRoute || previousRoute.versions === 'all') { + return true; + } + + return !this.routeSupportsVersion(previousRoute, previousLatest.value, previousLatest.value); + }); + + if (routesThatNeedUpdate.length) { + throw new Error( + [ + `There are routes that have been used under the previous version "${previousLatest.value}"`, + 'To continue, you either need to:', + '- Define that the route supports the previous version as well as \'latest\'', + '- Define that it only supports the previous version and optionally create a separate endpoint for the new version', + 'This can be done on the @Get/@Post etc. decorators or on the @Controller/@WebsocketController decorators', + '', + 'Affected routes/websocket events:', + ...routesThatNeedUpdate.map(r => `- ${r.key}`) + ].join('\n') + ); + } + } + + // eslint-disable-next-line jsdoc/require-jsdoc + resolveVersion(context: HttpRequestContext | WebsocketRequestContext): Version { + const versionValue: string | undefined = context instanceof WebsocketRequestContext + ? context.connection?.resolvedVersion.value ?? context.request.headers?.[this.versionHeader] + : context.request.headers?.[this.versionHeader]; + + try { + if (versionValue == undefined) { + const latest: VersionFile | undefined = this.versionFiles.find(v => v.endsAt == undefined); + if (!latest) { + throw new BadRequestError('No active version found'); + } + return latest; + } + if (isDate(versionValue)) { + return this.resolveVersionForDate(new Date(versionValue), this.versionFiles); + } + const res: VersionFile | undefined = this.versionFiles.find(v => v.value === versionValue); + if (!res) { + throw new BadRequestError(`Version "${versionValue}" not found`); + } + return res; + } + catch (error) { + if (error instanceof BadRequestError) { + throw error; + } + throw new BadRequestError('Could not resolve version'); + } + } + + // eslint-disable-next-line jsdoc/require-jsdoc + findOverlappingVersions( + a: SupportedVersionsOptions, + b: SupportedVersionsOptions, + currentLatest: SemVerVersion + ): SupportedVersionsOptions { + if (a === 'all' || b === 'all') { + return 'all'; + } + + const resolvedA: SemVerMatcher[] = a.map(v => this.versionMatcherToSemVerMatcher(v, currentLatest)); + const resolvedB: SemVerMatcher[] = b.map(v => this.versionMatcherToSemVerMatcher(v, currentLatest)); + + return resolvedA.filter(v => SemVerUtilities.matches(v, resolvedB)); + } + + private versionMatcherToSemVerMatcher(version: VersionMatcher, currentLatest: SemVerVersion): SemVerMatcher { + if (version === 'latest') { + return currentLatest; + } + if (version === '^latest') { + return `^${currentLatest}`; + } + if (version === '~latest') { + return `~${currentLatest}`; + } + + return version; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + hasOverlappingVersions( + a: SupportedVersionsOptions, + b: SupportedVersionsOptions, + currentLatest: SemVerVersion + ): boolean { + const result: SupportedVersionsOptions = this.findOverlappingVersions(a, b, currentLatest); + return result === 'all' || !!result.length; + } + + // eslint-disable-next-line jsdoc/require-jsdoc + matchesVersion(versions: SupportedVersionsOptions, resolvedVersion: Version): boolean { + if (versions === 'all') { + return true; + } + + const latest: VersionFile | undefined = this.versionFiles.find(v => v.endsAt == undefined); + if (!latest) { + throw new BadRequestError('No active version found'); + } + const resolvedMatchers: SemVerMatcher[] = versions.map( + v => this.versionMatcherToSemVerMatcher(v, latest.value) + ); + + return SemVerUtilities.matches(resolvedVersion.value, resolvedMatchers); + } + + // eslint-disable-next-line jsdoc/require-jsdoc + getVersions(): VersionFile[] { + return this.versionFiles; + } + + private resolveVersionForDate(date: Date, allVersions: VersionFile[]): Version { + const res: VersionFile | undefined = allVersions.find(v => { + return (new Date(v.startsAt).getTime() <= date.getTime()) + && (v.endsAt == undefined || new Date(v.endsAt).getTime() > date.getTime()); + }); + if (!res) { + throw new BadRequestError('No version active at that date'); + } + return res; + } + + private routeSupportsVersion( + route: RouteWithVersionData, + version: SemVerVersion, + latestContext: SemVerVersion + ): boolean { + if (route.versions === 'all') { + return true; + } + + const resolved: SemVerMatcher[] = route.versions.map(v => this.versionMatcherToSemVerMatcher(v, latestContext)); + + return SemVerUtilities.matches(version, resolved); + } + + private getAllRoutes(app: ZibriApplication): RouteWithVersionData[] { + const allRoutes: RouteWithVersionData[] = [ + ...this.router.manuallyRegisteredRoutes.map(r => ({ key: `${r.httpMethod.toUpperCase()} ${r.route}`, versions: r.versions })), + ...app.options.controllers.flatMap(c => { + const controllerData: ControllerData | undefined = MetadataUtilities.getControllerData(c); + if (!controllerData) { + throw new Error('Could not resolve controller data'); + } + const routes: ControllerRouteConfiguration[] = MetadataUtilities.getControllerRoutes(c); + return routes.map(r => ({ + versions: r.versions ?? controllerData.versions, + key: controllerData.baseRoute === '/' + ? `${r.httpMethod.toUpperCase()} ${r.route}` + : `${r.httpMethod.toUpperCase()} ${controllerData.baseRoute}${r.route}` + })); + }), + ...app.options.websocketControllers.flatMap(c => { + const controllerData: WebsocketControllerData | undefined = MetadataUtilities.getWebsocketControllerData(c); + if (!controllerData) { + throw new Error('Could not resolve controller data'); + } + const routes: WebsocketControllerRouteConfiguration[] = MetadataUtilities.getWebsocketControllerRoutes(c); + return routes.map(r => ({ + versions: r.versions ?? controllerData.versions, + key: `${controllerData.eventPrefix}${r.event}` + })); + }) + ]; + return allRoutes; + } +} \ No newline at end of file diff --git a/src/websocket/decorators/websocket-controller.decorator.ts b/src/websocket/decorators/websocket-controller.decorator.ts index a5077af..1b6ff78 100644 --- a/src/websocket/decorators/websocket-controller.decorator.ts +++ b/src/websocket/decorators/websocket-controller.decorator.ts @@ -1,15 +1,25 @@ import { GlobalRegistry } from '../../global/global-registry'; import { Newable } from '../../types/newable.type'; import { MetadataUtilities } from '../../utilities/metadata.utilities'; +import { SupportedVersionsOptions } from '../../versioning/supported-versions-options.model'; /** * Data of a websocket controller. */ export type WebsocketControllerData = { + /** + * A prefix that all events of the controller should have. + * @default '' + */ + eventPrefix: string, /** * Whether or not this websocket controller is allowed to exist without being registered in the application. */ - allowOrphan: boolean + allowOrphan: boolean, + /** + * The versions that should be supported by this websocket controller's events. Can be overridden per event. + */ + versions: SupportedVersionsOptions }; /** @@ -17,12 +27,12 @@ export type WebsocketControllerData = { * @param options - Options for the websocket controller. */ export function WebsocketController(options: Partial = {}): ClassDecorator { - const { allowOrphan = false } = options; + const { allowOrphan = false, versions = ['^latest'], eventPrefix = '' } = options; return target => { // eslint-disable-next-line unicorn/error-message const stack: string = new Error().stack ?? ''; MetadataUtilities.setFilePath(target, stack); - MetadataUtilities.setWebsocketControllerData(target, { allowOrphan }); + MetadataUtilities.setWebsocketControllerData(target, { allowOrphan, versions, eventPrefix }); GlobalRegistry.injectables.push({ token: target as unknown as Newable, useClass: target as unknown as Newable diff --git a/src/websocket/decorators/websocket-route.decorator.ts b/src/websocket/decorators/websocket-route.decorator.ts index 9a8f349..67d914b 100644 --- a/src/websocket/decorators/websocket-route.decorator.ts +++ b/src/websocket/decorators/websocket-route.decorator.ts @@ -1,18 +1,31 @@ import { MetadataUtilities } from '../../utilities/metadata.utilities'; +import { SupportedVersionsOptions } from '../../versioning/supported-versions-options.model'; import { WebsocketControllerRouteConfiguration } from '../models/websocket-controller-route-configuration.model'; +/** + * Options for a websocket route. + */ +export type WebsocketRouteOptions = { + /** + * The supported versions of this route. + * Defaults to '^latest'. + */ + versions?: SupportedVersionsOptions +}; + /** * Defines a route to receive websocket messages. * @param event - The event to listen on. + * @param options - The options of the route, like eg. The supported versions. */ -export function WebsocketRoute(event: string): MethodDecorator { +export function WebsocketRoute(event: string, options: WebsocketRouteOptions = {}): MethodDecorator { return (target, propertyKey) => { const ctor: Function = target.constructor; // eslint-disable-next-line unicorn/error-message const stack: string = new Error().stack ?? ''; MetadataUtilities.setFilePath(ctor, stack); const routes: WebsocketControllerRouteConfiguration[] = MetadataUtilities.getWebsocketControllerRoutes(ctor); - routes.push({ event, controllerMethod: propertyKey.toString() }); + routes.push({ event, controllerMethod: propertyKey.toString(), versions: options.versions }); MetadataUtilities.setWebsocketControllerRoutes(ctor, routes); }; } \ No newline at end of file diff --git a/src/websocket/models/connection/base-websocket-connection.model.ts b/src/websocket/models/connection/base-websocket-connection.model.ts index c03ebcb..c7f1fac 100644 --- a/src/websocket/models/connection/base-websocket-connection.model.ts +++ b/src/websocket/models/connection/base-websocket-connection.model.ts @@ -1,3 +1,5 @@ +import { Version } from '../../../versioning/version.model'; + /** * Basic definition of a websocket connection. * Your implementation probably needs to extend this. @@ -11,6 +13,10 @@ export type BaseWebsocketConnection = { * The id of the user that this connection belongs to. */ readonly userId: string | undefined, + /** + * The resolved version of the connection. + */ + readonly resolvedVersion: Version, /** * The current offset of the connection. * Is used to sync the state of the server with the client after a reconnect. diff --git a/src/websocket/models/connection/socket-io-websocket-connection.model.ts b/src/websocket/models/connection/socket-io-websocket-connection.model.ts index 1796de9..ae3130a 100644 --- a/src/websocket/models/connection/socket-io-websocket-connection.model.ts +++ b/src/websocket/models/connection/socket-io-websocket-connection.model.ts @@ -1,6 +1,7 @@ import { Socket } from 'socket.io'; import { BaseWebsocketConnection } from './base-websocket-connection.model'; +import { Version } from '../../../versioning/version.model'; import { LooseWebsocketEvent, WebsocketEvent } from '../websocket-event.enum'; import { WebsocketMessage } from '../websocket-message.model'; @@ -15,6 +16,7 @@ export class SocketIOWebsocketConnection implements BaseWebsocketConnection { * In that case, any missed packets will be transmitted to the client, the data attribute and the rooms will be restored. */ readonly recovered: boolean; + // eslint-disable-next-line jsdoc/require-jsdoc get offset(): number { if (typeof this.socket.handshake.auth.offset !== 'number') { @@ -26,7 +28,12 @@ export class SocketIOWebsocketConnection implements BaseWebsocketConnection { this.socket.handshake.auth.offset = value; } - constructor(private readonly socket: Socket, public userId: string | undefined) { + constructor( + private readonly socket: Socket, + public userId: string | undefined, + readonly resolvedVersion: Version + + ) { this.id = this.socket.id; this.recovered = this.socket.recovered; } diff --git a/src/websocket/models/websocket-controller-route-configuration.model.ts b/src/websocket/models/websocket-controller-route-configuration.model.ts index 1fe760b..40be11a 100644 --- a/src/websocket/models/websocket-controller-route-configuration.model.ts +++ b/src/websocket/models/websocket-controller-route-configuration.model.ts @@ -1,3 +1,5 @@ +import { SupportedVersionsOptions } from '../../versioning/supported-versions-options.model'; + /** * The configuration for a websocket controller route. */ @@ -9,5 +11,10 @@ export type WebsocketControllerRouteConfiguration = { /** * The name of the method on the controller that is responsible for handling messages to the websocket event. */ - controllerMethod: string + controllerMethod: string, + /** + * The supported versions of this route. + * Defaults to '^latest'. + */ + versions: SupportedVersionsOptions | undefined }; \ No newline at end of file diff --git a/src/websocket/models/websocket-message.model.ts b/src/websocket/models/websocket-message.model.ts index 43a505d..e9d3de9 100644 --- a/src/websocket/models/websocket-message.model.ts +++ b/src/websocket/models/websocket-message.model.ts @@ -56,12 +56,12 @@ export class WebsocketMessage extends BaseEntity { * The id of the user that has sent the message. */ @Property.string({ required: false, format: 'uuid' }) - senderUserId: string | undefined; + senderUserId: string | undefined | null; /** * The of the connection that has sent the message. */ @Property.string({ required: false }) - senderConnectionId: string | undefined; + senderConnectionId: string | undefined | null; /** * The actual data of the message. */ diff --git a/src/websocket/models/websocket-request.model.ts b/src/websocket/models/websocket-request.model.ts index d56a011..e094581 100644 --- a/src/websocket/models/websocket-request.model.ts +++ b/src/websocket/models/websocket-request.model.ts @@ -1,5 +1,6 @@ import { BaseWebsocketConnection } from './connection/base-websocket-connection.model'; import { Property } from '../../entity/decorators/property.decorator'; +import { Header } from '../../http/header.type'; import { HttpRequest } from '../../http/http-request.model'; import { KnownHeader } from '../../http/known-header.enum'; @@ -23,7 +24,7 @@ export class WebsocketRequest< T = unknown, PathParamsObject extends Record = Record, QueryParamsObject extends Record = Record, - HeaderParamsObject extends Record = Partial> + HeaderParamsObject extends Record = Partial> > implements Partial, 'headers' | 'body' | 'query' | 'params'> @@ -33,8 +34,8 @@ export class WebsocketRequest< query: QueryParamsObject | undefined; // eslint-disable-next-line jsdoc/require-jsdoc - @Property.object({ cls: () => HeadersObject, allowAdditionalProperties: true }) - headers!: HeaderParamsObject; + @Property.object({ cls: () => HeadersObject, required: false, allowAdditionalProperties: true }) + headers: HeaderParamsObject | undefined; // eslint-disable-next-line jsdoc/require-jsdoc @Property.object({ cls: () => ParamsObject, required: false, allowAdditionalProperties: true }) diff --git a/src/websocket/services/websocket.service.ts b/src/websocket/services/websocket.service.ts index ac5e080..5ab4e9d 100644 --- a/src/websocket/services/websocket.service.ts +++ b/src/websocket/services/websocket.service.ts @@ -1,3 +1,5 @@ +import assert from 'node:assert'; + import { Server, Socket } from 'socket.io'; import { WebsocketSendData, WebsocketSendDataMessage, WebsocketSendToAllData, WebsocketSendToChannelData, WebsocketServiceInterface } from './websocket-service.interface'; @@ -17,6 +19,7 @@ import { toHttpError } from '../../error-handling/error-handler'; import { BadRequestError } from '../../error-handling/errors/bad-request.error'; import { HttpError, isHttpError } from '../../error-handling/errors/http.error'; import { NotFoundError } from '../../error-handling/errors/not-found.error'; +import { UnauthorizedError } from '../../error-handling/errors/unauthorized.error'; import { isError } from '../../error-handling/is-error.function'; import { BeforeAppShutdown } from '../../global/before-app-shutdown.interface'; import { GlobalRegistry } from '../../global/global-registry'; @@ -24,13 +27,17 @@ import { OnAppInit } from '../../global/on-app-init.interface'; import { HttpStatus } from '../../http/http-status.enum'; import { KnownHeader } from '../../http/known-header.enum'; import { type LoggerInterface } from '../../logging/logger.interface'; -import { type ParserInterface } from '../../parsing/parser.interface'; import { resolveRouteParams } from '../../routing/resolve-route-params.function'; import { Newable } from '../../types/newable.type'; import { JsonUtilities } from '../../utilities/json.utilities'; import { MetadataUtilities } from '../../utilities/metadata.utilities'; +import { SemVerVersion } from '../../utilities/sem-ver.utilities'; import { UUIDUtilities } from '../../utilities/uuid.utilities'; import { type ValidationServiceInterface } from '../../validation/validation-service.interface'; +import { RouteWithVersionData } from '../../versioning/route-with-version-data.model'; +import { SupportedVersionsOptions } from '../../versioning/supported-versions-options.model'; +import { Version } from '../../versioning/version.model'; +import { type VersioningServiceInterface } from '../../versioning/versioning-service.interface'; import { WebsocketControllerData } from '../decorators/websocket-controller.decorator'; import { SocketIOWebsocketConnection } from '../models/connection/socket-io-websocket-connection.model'; import { WebsocketChannel } from '../models/websocket-channel.model'; @@ -57,17 +64,25 @@ type SocketIOWebsocketHandler = ( @Injectable({ register: 'onUse' }) export class WebsocketService implements WebsocketServiceInterface, OnAppInit, BeforeAppShutdown { private socketServer!: Server; + private readonly allEventRoutes: RouteWithVersionData[] = []; private readonly websocketHandlers: Record = {}; private readonly websocketChannels: Record = {}; private readonly connections: SocketIOWebsocketConnection[] = []; + private readonly pendingRouteGroups: Map = new Map(); + + private get versioningService(): VersioningServiceInterface { + return inject(ZIBRI_DI_TOKENS.VERSIONING_SERVICE); + } + constructor( @Inject(ZIBRI_DI_TOKENS.LOGGER) private readonly logger: LoggerInterface, @Inject(ZIBRI_DI_TOKENS.AUTH_SERVICE) private readonly authService: AuthServiceInterface, - @Inject(ZIBRI_DI_TOKENS.PARSER) - private readonly parser: ParserInterface, @Inject(ZIBRI_DI_TOKENS.VALIDATION_SERVICE) private readonly validationService: ValidationServiceInterface, @InjectRepository(WebsocketChannel) @@ -92,7 +107,44 @@ export class WebsocketService implements WebsocketServiceInterface this.onConnect(socket)); + for (const [event, group] of this.pendingRouteGroups.entries()) { + this.websocketHandlers[event] = this.createDispatchHandler(group.entries); + await this.logger.debug(`- mounting websocket event "${event}"`); + } + + // eslint-disable-next-line typescript/no-misused-promises + this.socketServer.use(async (socket, next) => { + const request: WebsocketRequest = { + headers: socket.handshake.headers as Partial>, + body: undefined, + query: socket.handshake.query as Partial>, + params: {} + }; + const context: WebsocketRequestContext = new WebsocketRequestContext(request, undefined, undefined, undefined); + + const currentUser: BaseUser | undefined = await this.authService.getCurrentUser( + context, + this.authService.strategies, + false + ); + if (!await this.options.isAllowedToConnect(currentUser)) { + next(new UnauthorizedError('Not allowed to connect')); + return; + } + + try { + const version: Version = await this.versioningService.resolveVersion(context); + // eslint-disable-next-line typescript/no-unsafe-member-access + socket.data.resolvedVersion = version; + // eslint-disable-next-line typescript/no-unsafe-member-access + socket.data.currentUser = currentUser; + next(); + } + catch (error) { + next(error instanceof Error ? error : new Error('Could not resolve version', { cause: error })); + } + }); + this.socketServer.on('connection', async socket => await this.onConnect(socket)); } // eslint-disable-next-line jsdoc/require-jsdoc @@ -101,26 +153,12 @@ export class WebsocketService implements WebsocketServiceInterface { - const request: WebsocketRequest = { - headers: socket.handshake.headers as Partial>, - body: undefined, - query: socket.handshake.query as Partial>, - params: {} - }; - let connection: SocketIOWebsocketConnection | undefined = this.connections.find(c => c.id === socket.id); - - const context: WebsocketRequestContext = new WebsocketRequestContext(request, connection, undefined, undefined); - const currentUser: BaseUser | undefined = await this.authService.getCurrentUser( - context, - this.authService.strategies, - false - ); + // eslint-disable-next-line typescript/no-unsafe-member-access + const currentUser: BaseUser | undefined = socket.data.currentUser as BaseUser | undefined; + // eslint-disable-next-line typescript/no-unsafe-member-access + const resolvedVersion: Version = socket.data.resolvedVersion as Version; - const isAllowedToConnect: boolean = await this.options.isAllowedToConnect(currentUser); - if (!isAllowedToConnect) { - socket.disconnect(true); - return; - } + let connection: SocketIOWebsocketConnection | undefined = this.connections.find(c => c.id === socket.id); if (connection) { connection.offset = socket.handshake.auth.offset as number; @@ -128,7 +166,7 @@ export class WebsocketService implements WebsocketServiceInterface): Promise { const controllerData: WebsocketControllerData | undefined = MetadataUtilities.getWebsocketControllerData(controllerClass); + const currentLatest: SemVerVersion | undefined = GlobalRegistry.getAppData('version'); + assert(currentLatest); if (controllerData == undefined) { // eslint-disable-next-line stylistic/max-len throw new Error(`Could not find websocket controller data on class ${controllerClass.name}. Did you forget to decorate it with @WebsocketController?`); @@ -291,19 +331,44 @@ export class WebsocketService implements WebsocketServiceInterface r.key === fullEvent && this.versioningService.hasOverlappingVersions(r.versions, versions, currentLatest) ); - if (this.websocketHandlers[route.event]) { - throw new Error( - `The websocket event "${route.event}" has been defined more than once.`, - { cause: controllerClass } + if (overlappingRoute) { + const overlappingVersions: SupportedVersionsOptions = this.versioningService.findOverlappingVersions( + overlappingRoute.versions, + versions, + currentLatest ); + if (overlappingVersions === 'all') { + throw new Error([ + `The websocket event "${fullEvent}"`, + 'has been defined more than once.', + '(versions: \'all\' has been used)' + ].join(' '), { cause: controllerClass }); + } + throw new Error([ + `The websocket event "${fullEvent}"`, + `for the ${overlappingVersions.length > 1 ? 'versions' : 'version'} "${overlappingVersions.join(', ')}"`, + 'has been defined more than once.' + ].join(' '), { cause: controllerClass }); } - await this.logger.debug(`- mounting websocket event "${route.event}"`); - this.websocketHandlers[route.event] = handler; + this.allEventRoutes.push({ key: fullEvent, versions }); + + const innerHandler: SocketIOWebsocketHandler = this.controllerRouteToWebsocketHandler(controllerClass, route); + // eslint-disable-next-line typescript/typedef + const existing = this.pendingRouteGroups.get(fullEvent); + if (existing) { + existing.entries.push({ versions, innerHandler }); + } + else { + this.pendingRouteGroups.set(fullEvent, { entries: [{ versions, innerHandler }] }); + } + await this.logger.debug(`- registering websocket event "${fullEvent}"`); } } @@ -358,7 +423,7 @@ export class WebsocketService implements WebsocketServiceInterface( data: WebsocketSendToChannelData ): Promise[]> { - data.expectResponse ??= true as B; + data.expectResponse ??= false as B; const channel: WebsocketChannel = await this.channelRepository.findById(data.channelId); const message: WebsocketMessage = await this.createWebsocketMessage( @@ -422,9 +487,9 @@ export class WebsocketService implements WebsocketServiceInterface( - data: WebsocketSendToAllData, - expectResponse: B = true as B + data: WebsocketSendToAllData ): Promise[]> { + data.expectResponse ??= false as B; const message: WebsocketMessage = await this.createWebsocketMessage( data.persist ?? true, data.message.ok @@ -450,7 +515,7 @@ export class WebsocketService implements WebsocketServiceInterface[]; } - if (!expectResponse) { + if (!data.expectResponse) { this.socketServer.emit(data.event, JsonUtilities.parse(JsonUtilities.stringify(message))); return undefined as B extends false ? void : WebsocketRequestWithConnection[]; } @@ -514,7 +579,9 @@ export class WebsocketService implements WebsocketServiceInterface c.id === channel.id); + const foundChannel: SocketIOWebsocketConnection | undefined = this.websocketChannels[channel.name]?.find( + c => c.id === connection.id + ); if (foundChannel) { // eslint-disable-next-line typescript/no-non-null-assertion this.websocketChannels[channel.name]!.splice(this.websocketChannels[channel.name]!.indexOf(foundChannel), 1); @@ -544,6 +611,44 @@ export class WebsocketService implements WebsocketServiceInterface { + try { + // eslint-disable-next-line typescript/typedef + const match = entries.find(e => this.versioningService.matchesVersion(e.versions, connection.resolvedVersion)); + if (!match) { + const error: NotFoundError = new NotFoundError( + `Could not find handler for websocket event for version "${connection.resolvedVersion.value}"` + ); + await this.send({ + connection, + event: WebsocketEvent.RESPONSE, + message: { ok: false, error, status: error.status, senderUserId: undefined, senderConnectionId: undefined }, + responseHandler, + expectResponse: true, + persist: false + }); + return; + } + await match.innerHandler(connection, req, responseHandler); + } + catch (error) { + const err: HttpError = toHttpError(error); + await this.send({ + connection, + event: WebsocketEvent.RESPONSE, + message: { ok: false, error: err, status: err.status, senderUserId: undefined, senderConnectionId: undefined }, + responseHandler, + expectResponse: true, + persist: false + }); + } + }; + } + private async createWebsocketMessage( persist: boolean, messageData: CreateWebsocketMessageData | WebsocketMessage From 82692906c99df3bc1956864be83ef27d407e3f84 Mon Sep 17 00:00:00 2001 From: Tim Fabian Date: Sat, 30 May 2026 16:10:12 +0200 Subject: [PATCH 2/8] improved docs, added fuzzyLike filter for strings, orders open api things alphabetically --- docs/caching.md | 71 +++++++++++++++++++ docs/cron.md | 6 +- docs/data-source.md | 2 +- docs/di.md | 5 +- docs/encryption-and-hashing.md | 22 ++++++ docs/excluding-properties.md | 17 +++++ docs/metrics.md | 6 +- docs/templating.md | 6 +- docs/versioning.md | 45 ++++++++++++ sandbox/package-lock.json | 2 +- sandbox/src/cron/status.cron-job.ts | 4 +- .../postgres-typeorm-data-source.model.ts | 10 +++ ...postgres-typeorm-where-filter.converter.ts | 37 +++++++++- .../typeorm-where-filter.converter.ts | 12 ++++ .../models/where/string-where-filter.model.ts | 19 ++++- .../models/where/where-filter-keys.model.ts | 1 + src/data-source/nested-where-filter.test.ts | 7 ++ src/data-source/where-filter.test.ts | 17 +++++ .../formatting/format-price.function.ts | 5 +- src/open-api/open-api.service.ts | 13 +++- 20 files changed, 286 insertions(+), 21 deletions(-) create mode 100644 docs/caching.md create mode 100644 docs/encryption-and-hashing.md create mode 100644 docs/excluding-properties.md create mode 100644 docs/versioning.md diff --git a/docs/caching.md b/docs/caching.md new file mode 100644 index 0000000..5409a55 --- /dev/null +++ b/docs/caching.md @@ -0,0 +1,71 @@ +# Caching +Caching in Zibri is based on first creating a cache class and then using that class with the help of decorators. + +## Defining a cache +Zibri provides caches for all combinations of write and read strategies. + +```ts +import { Cache, CacheServiceInterface, HtmlResponse, Inject, InMemoryCacheStore, LoggerInterface, MetricsServiceInterface, WriteThroughReadThroughCache, ZIBRI_DI_TOKENS } from 'zibri'; + +@Cache() +export class StaticPagesCache extends WriteThroughReadThroughCache { + constructor( + @Inject(ZIBRI_DI_TOKENS.LOGGER) + protected readonly logger: LoggerInterface, + @Inject(ZIBRI_DI_TOKENS.CACHE_SERVICE) + protected readonly cacheService: CacheServiceInterface, + @Inject(ZIBRI_DI_TOKENS.METRICS_SERVICE) + protected readonly metricsService: MetricsServiceInterface + ) { + super('StaticPagesCache', new InMemoryCacheStore(), []); + } +} +``` + +## Using a cache +As you can see, the above uses a really simple in memory cache store. But you could also provide your own here, based eg. on redis. + +To use your cache, you can use the provided decorators on whichever method that should be cached: + +```ts +import { Cached, Get, GlobalRegistry, HtmlResponse, PreactUtilities, Response } from 'zibri'; + +import { HomePage } '../home'; + +@Cached(StaticPagesCache, () => 'index') +@Response.html() +@Get() +async index(): Promise { + const html: string = await PreactUtilities.renderPage(HomePage, { appName: GlobalRegistry.getAppData('name') ?? '' }); + return HtmlResponse.fromString(html); +} +``` + +Other decorators include: +- CacheDelete +- CacheInvalidate +- CacheWrite + +## Multi Tier Caches +Multi Tier Caches are natively built into Zibri. They provide a clean way to define:
+Use the in memory cache if available. If it's not available: Look it up inside the redis cache. If that's also not available: Actually run the underlying method and fill all caches. + +```ts +@Cache() +class TestMultiTierCache extends MultiTierCache { + constructor( + @Inject(ZIBRI_DI_TOKENS.LOGGER) + protected readonly logger: LoggerInterface, + @Inject(ZIBRI_DI_TOKENS.METRICS_SERVICE) + protected readonly metricsService: MetricsServiceInterface, + @Inject(ZIBRI_DI_TOKENS.CACHE_SERVICE) + protected readonly cacheService: CacheServiceInterface, + @Inject(FastCache) + fast: FastCache, + @Inject(SlowCache) + slow: SlowCache + ) { + super('TestMulti', [fast, slow]); + } +} +``` \ No newline at end of file diff --git a/docs/cron.md b/docs/cron.md index c1777e9..e27d502 100644 --- a/docs/cron.md +++ b/docs/cron.md @@ -6,18 +6,18 @@ To define a job it needs to extend the `CronJob` class. Below is a simple exampl ```ts // src/cron/status.cron-job.ts -import { CronJob, inject, Injectable, LoggerInterface, ZIBRI_DI_TOKENS, InitialCronConfig } from 'zibri'; +import { CronExpression, CronJob, inject, Injectable, ZIBRI_DI_TOKENS, InitialCronConfig } from 'zibri'; @Injectable() export class StatusCronJob extends CronJob { readonly initialConfig: InitialCronConfig = { name: 'Status', - cron: '* * * * * *', + cron: CronExpression.every(1, 'seconds').build(), active: false }; async onTick(): Promise { - await inject(ZIBRI_DI_TOKENS.LOGGER).info(`is running ${this.name}`); + await inject(ZIBRI_DI_TOKENS.LOGGER).info(`is running ${this.name}`); } } ``` diff --git a/docs/data-source.md b/docs/data-source.md index 5c06753..5ec78c1 100644 --- a/docs/data-source.md +++ b/docs/data-source.md @@ -23,7 +23,7 @@ import { Test } from '../../models'; @DataSource() export class DbDataSource extends PostgresDataSource { - options: PostgresOptions = { + options: OmitStrict = { host: 'localhost', port: 5432, username: 'postgres', diff --git a/docs/di.md b/docs/di.md index b33ed2c..8fa3600 100644 --- a/docs/di.md +++ b/docs/di.md @@ -81,10 +81,9 @@ Let's say that you want for example to replace the default error handler with th ```ts // src/my-error-handler.ts -import { NextFunction } from 'express'; -import { GlobalErrorHandler, HttpRequest, HttpResponse } from 'zibri'; +import { GlobalErrorHandler } from 'zibri'; -export const myErrorHandler: GlobalErrorHandler = async (error: unknown, req: HttpRequest, res: HttpResponse, next: NextFunction) => { +export const myErrorHandler: GlobalErrorHandler = async (error, req, res, next) => { // ...your custom logic } ``` diff --git a/docs/encryption-and-hashing.md b/docs/encryption-and-hashing.md new file mode 100644 index 0000000..f07aba1 --- /dev/null +++ b/docs/encryption-and-hashing.md @@ -0,0 +1,22 @@ +# Encryption & Hashing +Encryption and hashing of entity properties can be easily defined via the `@Property.string` decorator: + +```ts +// Alternatively you can also provide an options object to encryption instead of the boolean flag. +@Property.string({ encryption: true }) +encryptedValue!: string; + +// Alternatively you can also provide an options object to hash instead of the boolean flag. +@Property.string({ hash: true }) +hashedValue!: HashString; +``` + +This will encrypt/hash any values that are stored in a data source via a repository. + +## Decryption +When using the `@Property` decorator, encrypted values are automatically decrypted when read from the datasource. This can be configured when using the options object instead of the simple boolean flag. This configuration is pretty flexible with a callback, it allows for example to decrypt based on the current users role. So you could specify that Admins are allowed to decrypt, but normal Users aren't. + +Alternatively and if sufficient for your use case, the [exclude functionality](./excluding-properties.md) can be used for this as well or in addition. + +## Manual encryption/hashing +To manually encrypt or hash a value Zibri provides the `ZIBRI_DI_TOKENS.ENCRYPTION_SERVICE` and `ZIBRI_DI_TOKENS.HASH_SERVICE` injection tokens, as well as `.HASH_STRATEGIES` and `ENCRYPTION_STRATEGIES` if you simply want a different algorithm. By default bcrypt and aes-256-gcm are used. \ No newline at end of file diff --git a/docs/excluding-properties.md b/docs/excluding-properties.md new file mode 100644 index 0000000..c179c68 --- /dev/null +++ b/docs/excluding-properties.md @@ -0,0 +1,17 @@ +# Excluding properties +At some point in time you will probably stumble across the problem of having some sensitive data like eg. a password where you want to make sure that it will never leave the server in some form. Be it via http, websocket connection or inside of logs. + +To support that use case, Zibri provides a exclude flag: + +```ts +// Alternatively you can also provide an options object instead of the boolean flag. +@Property.string({ hash: true, exclude: true }) +password!: HashString; +``` + +The configuration object that can be used instead of the boolean flag here is pretty flexible with a callback. It allows for example to exclude based on the current users role. So you could specify that for Admins the password hash is not excluded, but for normal Users it is. + +## CAVEAT: What this actually does +Be aware that this does NOT remove the property as soon as the result comes back from your repository call. It simply marks them as not enumarable, which results in the property never showing up when things like JSON.stringify etc. are used. + +This allows you to work with the value while it's still on the server. If you have a login endpoint for example, you can access the password hash to compare it to the user input. But if you return the current user data afterwards, the password is removed. \ No newline at end of file diff --git a/docs/metrics.md b/docs/metrics.md index 790d169..08cb9a0 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -44,7 +44,11 @@ export class MetricsController { @Get('/dashboard') async dashboard(): Promise { const version: string = GlobalRegistry.getAppData('version') ?? '-'; - return await PreactUtilities.renderResponse(MetricsPage, { version, primary: '#0e456f', secondary: '#00b4d8' }); + const html: string = await PreactUtilities.renderPage( + MetricsPage, + { version, cacheNames, primary: '#0e456f', secondary: '#00b4d8' } + ); + return HtmlResponse.fromString(html); } } ``` diff --git a/docs/templating.md b/docs/templating.md index 28368d6..5c14cd9 100644 --- a/docs/templating.md +++ b/docs/templating.md @@ -51,7 +51,11 @@ export class PageController { @Response.html() @Get() async index(): Promise { - return await PreactUtilities.renderResponse(HomePage, { appName: GlobalRegistry.getAppData('name') ?? '' }); + const html: string = await PreactUtilities.renderPage( + HomePage, + { appName: GlobalRegistry.getAppData('name') ?? '' } + ); + return HtmlResponse.fromString(html); } } ``` diff --git a/docs/versioning.md b/docs/versioning.md new file mode 100644 index 0000000..cbc68d1 --- /dev/null +++ b/docs/versioning.md @@ -0,0 +1,45 @@ +# Versioning +Zibri uses the `package.json` as the single source of truth for versioning concerns. + +It's used for [Handling migrations](./data-source.md#handling-migrations) and to check against endpoints supported versions. + +## Defining supported versions on endpoints +Supported versions can be defined with the same syntax you would use inside your package.json: + +```ts +@Controller('/some', { versions: ['^1.0.0'] }) +class ValidatedController { + @Get('/endpoint-v1') + getEndpoint(): { ok: boolean } { + return { ok: true }; + } + + @Get('/endpoint-v2', { versions: ['^2'] }) + getEndpointV2(): { ok: boolean } { + return { ok: true }; + } + + @Get('/endpoint-latest', { versions: ['^latest'] }) + getEndpointLatest(): { ok: boolean } { + return { ok: true }; + } +} +``` + +As you can see, versions can either be defined on the controller or on the route level. + +There is also the special `'latest'` version, which resolves to the current highest major version. By default controllers and routes support the version `'^latest'`. + +## Validation +Zibri aims to provide really strong guard rails when it comes to versioning. You might have already noticed that a `versions` folder in your project gets generated when you start it up the first time with a new `package.json` version. Inside the folder, each version is saved, in addition with its configured routes at that point in time. This allows for some really strong validation when the version inside the `package.json` is bumped. + +Let's say you start with version `'1.0.0'` (the default) and later on change it to `'2.0.0'`. Now Zibri can complain about any endpoint that previously used `'latest'`, `'^latest'` or `'~latest'`, because latest now means something else, so it should have something like `['^1.0.0', '^latest']` as the supported versions defined. Otherwise, any user of version `'1.0.0'` would get an error that the endpoints they spoke to just fine now no longer exist. + +# Version resolution +Versions are resolved by reading from a custom header (`'x-version'` by default). The provided value can either be: +- a concrete version, like `'1.0.0'` +- a json date, like `'2026-05-29T07:44:06.186Z'` + +Zibris versioning service then handles resolving the correct version and endpoint for that header. Or throwing an error when it could not be found. + +What version is resolved by the date is defined inside of the version files: They contain a startsAt and endsAt timestamp. \ No newline at end of file diff --git a/sandbox/package-lock.json b/sandbox/package-lock.json index 635815f..d36fecb 100644 --- a/sandbox/package-lock.json +++ b/sandbox/package-lock.json @@ -30,7 +30,7 @@ } }, "..": { - "version": "2.4.1", + "version": "2.5.0", "license": "MIT", "dependencies": { "@fastify/busboy": "^3.2.0", diff --git a/sandbox/src/cron/status.cron-job.ts b/sandbox/src/cron/status.cron-job.ts index 083de9e..3bb0d0f 100644 --- a/sandbox/src/cron/status.cron-job.ts +++ b/sandbox/src/cron/status.cron-job.ts @@ -1,4 +1,4 @@ -import { CronJob, inject, LoggerInterface, ZIBRI_DI_TOKENS, InitialCronConfig, CronExpression } from 'zibri'; +import { CronJob, inject, ZIBRI_DI_TOKENS, InitialCronConfig, CronExpression } from 'zibri'; export class StatusCronJob extends CronJob { readonly initialConfig: InitialCronConfig = { @@ -8,6 +8,6 @@ export class StatusCronJob extends CronJob { }; async onTick(): Promise { - await inject(ZIBRI_DI_TOKENS.LOGGER).info(`is running ${this.name}`); + await inject(ZIBRI_DI_TOKENS.LOGGER).info(`is running ${this.name}`); } } \ No newline at end of file diff --git a/src/data-source/data-sources/postgres-typeorm-data-source.model.ts b/src/data-source/data-sources/postgres-typeorm-data-source.model.ts index 60ae077..f81ea52 100644 --- a/src/data-source/data-sources/postgres-typeorm-data-source.model.ts +++ b/src/data-source/data-sources/postgres-typeorm-data-source.model.ts @@ -73,6 +73,16 @@ export abstract class PostgresDataSource extends TypeOrmBaseDataSource { + await super.init(); + if (!this.ds) { + throw new DataSourceInitializationError(); + } + // eslint-disable-next-line cspell/spellchecker + await this.ds.query('CREATE EXTENSION IF NOT EXISTS pg_trgm'); + } + // eslint-disable-next-line jsdoc/require-jsdoc query(entityClass: Newable, options?: QueryOptions): QueryBuilder { if (!this.ds) { diff --git a/src/data-source/data-sources/where-converter/postgres-typeorm-where-filter.converter.ts b/src/data-source/data-sources/where-converter/postgres-typeorm-where-filter.converter.ts index fa4f1bc..a971035 100644 --- a/src/data-source/data-sources/where-converter/postgres-typeorm-where-filter.converter.ts +++ b/src/data-source/data-sources/where-converter/postgres-typeorm-where-filter.converter.ts @@ -1,7 +1,7 @@ import { Raw, FindOperator, DataSource as ToDataSource } from 'typeorm'; import { RelationMetadata as ToRelationMetadata } from 'typeorm/metadata/RelationMetadata.js'; -import { TypeOrmWhereFilterConverter } from './typeorm-where-filter.converter'; +import { TypeOrmWhereFilterConverter, WhereFilterHandler } from './typeorm-where-filter.converter'; import { BaseEntity } from '../../../entity/base-entity.model'; import { EntityMetadata } from '../../../entity/decorators/entity.decorator'; import { PropertyMetadata, RelationMetadata } from '../../../entity/decorators/property.decorator'; @@ -9,6 +9,7 @@ import { EntityMetadataMissingError } from '../../../entity/entity-metadata-miss import { Relation } from '../../../entity/models/relation.enum'; import { Newable } from '../../../types/newable.type'; import { MetadataUtilities } from '../../../utilities/metadata.utilities'; +import { NumberUtilities } from '../../../utilities/number.utilities'; import { WhereFilterKeys } from '../../models/where/where-filter-keys.model'; import { Where } from '../../models/where/where-filter.model'; @@ -32,7 +33,7 @@ type JsonbOperatorHandler = ( */ export class PostgresTypeOrmWhereFilterConverter extends TypeOrmWhereFilterConverter { - private readonly jsonbOperatorHandlers: Record = { + private readonly jsonbOperatorHandlers: Record = { is: (jp, _tp, _cp, val) => val === null ? `${jp} = 'null'::jsonb` : `${jp} @> ${this.toJsonbLiteral(val as object)}`, @@ -59,12 +60,27 @@ export class PostgresTypeOrmWhereFilterConverter extends TypeOrmWhereFilterConve }, like: (_jp, tp, _cp, val) => `${tp} LIKE ${this.toSqlLiteral(val)}`, iLike: (_jp, tp, _cp, val) => `${tp} ILIKE ${this.toSqlLiteral(val)}`, + fuzzyLike: (_jp, tp, _cp, val) => { + if (typeof val !== 'object' || !('value' in (val as object))) { + throw new Error('fuzzyLike expects an object with a "value" property'); + } + const { value: searchString, minSimilarity = 0.3 } = val as { + // eslint-disable-next-line jsdoc/require-jsdoc + value: string, + // eslint-disable-next-line jsdoc/require-jsdoc + minSimilarity?: number + }; + const safeSearch: string = this.toSqlLiteral(searchString); + return `similarity(${tp}, ${safeSearch}) >= ${NumberUtilities.divide(minSimilarity, 100)}`; + }, greaterThan: (_jp, _tp, cp, val) => `${cp} > ${this.toSqlLiteral(val)}`, after: (_jp, _tp, cp, val) => `${cp} > ${this.toSqlLiteral(val)}`, greaterThanEquals: (_jp, _tp, cp, val) => `${cp} >= ${this.toSqlLiteral(val)}`, + afterOrOn: (_jp, _tp, cp, val) => `${cp} >= ${this.toSqlLiteral(val)}`, lesserThan: (_jp, _tp, cp, val) => `${cp} < ${this.toSqlLiteral(val)}`, before: (_jp, _tp, cp, val) => `${cp} < ${this.toSqlLiteral(val)}`, lesserThanEquals: (_jp, _tp, cp, val) => `${cp} <= ${this.toSqlLiteral(val)}`, + beforeOrOn: (_jp, _tp, cp, val) => `${cp} <= ${this.toSqlLiteral(val)}`, length: (jp, _tp, _cp, val) => `jsonb_array_length(${jp}) = ${this.toSqlLiteral(val)}`, lengthGreaterThan: (jp, _tp, _cp, val) => `jsonb_array_length(${jp}) > ${this.toSqlLiteral(val)}`, lengthGreaterThanEquals: (jp, _tp, _cp, val) => `jsonb_array_length(${jp}) >= ${this.toSqlLiteral(val)}`, @@ -89,6 +105,23 @@ export class PostgresTypeOrmWhereFilterConverter extends TypeOrmWhereFilterConve super(dataSource); } + // eslint-disable-next-line jsdoc/require-jsdoc + protected fuzzyLikeWhereFilterHandler: WhereFilterHandler = (value) => { + if (typeof value !== 'object' || value === null || !('value' in value)) { + throw new Error('fuzzyLike expects an object with a "value" property'); + } + const { value: searchString, minSimilarity = 30 } = value as { + // eslint-disable-next-line jsdoc/require-jsdoc + value: string, + // eslint-disable-next-line jsdoc/require-jsdoc + minSimilarity?: number + }; + const safeSearch: string = this.toSqlLiteral(searchString); // produces e.g. 'hello' + return Raw( + alias => `similarity(${alias}, ${safeSearch}) >= ${NumberUtilities.divide(minSimilarity, 100)}` + ); + }; + // ── JSONB-aware "where" handler ───────────────────────────── // eslint-disable-next-line jsdoc/require-jsdoc diff --git a/src/data-source/data-sources/where-converter/typeorm-where-filter.converter.ts b/src/data-source/data-sources/where-converter/typeorm-where-filter.converter.ts index 1249499..905b15f 100644 --- a/src/data-source/data-sources/where-converter/typeorm-where-filter.converter.ts +++ b/src/data-source/data-sources/where-converter/typeorm-where-filter.converter.ts @@ -34,6 +34,12 @@ export type WhereFilterHandler = ( * Abstract base converter that transforms a Zibri WhereFilter into a TypeORM FindOptionsWhere. */ export abstract class TypeOrmWhereFilterConverter { + + /** + * Handler for a fuzzyLike filter. + */ + protected abstract fuzzyLikeWhereFilterHandler: WhereFilterHandler; + /** * A map that defines how Zibri's where filter properties are mapped to their typeorm counterpart. */ @@ -61,6 +67,12 @@ export abstract class TypeOrmWhereFilterConverter { lesserThan: (value) => LessThan(value), lesserThanEquals: (value) => LessThanOrEqual(value), iLike: (value) => ILike(value), + fuzzyLike: (value, metadata, nestedProperties, entityClass) => this.fuzzyLikeWhereFilterHandler( + value, + metadata, + nestedProperties, + entityClass + ), is: (value, metadata) => { if (value === null) { return IsNull(); diff --git a/src/data-source/models/where/string-where-filter.model.ts b/src/data-source/models/where/string-where-filter.model.ts index 66e7863..bd2f292 100644 --- a/src/data-source/models/where/string-where-filter.model.ts +++ b/src/data-source/models/where/string-where-filter.model.ts @@ -1,4 +1,6 @@ import { BaseWhereFilter, BaseWhereFilterObject } from './base-where-filter.model'; +import { ExcludeStrict } from '../../../types/exclude-strict.type'; +import { Percentage } from '../../../types/percentage.type'; /** * The string where filter object. @@ -13,7 +15,22 @@ type StringWhereFilterObject = BaseWhereFilterObject & { * The property needs to be like the provided string, ignoring case. * @example '%.cOm' */ - iLike?: string + iLike?: string, + /** + * The property needs to fuzzily be like the provided string so that eg. Typos don't matter. + */ + fuzzyLike?: { + /** + * The search value. + */ + value: string, + /** + * The minimum similarity that the search term must have as a percentage between 1 and 99. + * + * 1 matches almost everything while 99 is basically just a strict equal. + */ + minSimilarity?: ExcludeStrict + } }; /** diff --git a/src/data-source/models/where/where-filter-keys.model.ts b/src/data-source/models/where/where-filter-keys.model.ts index 120710b..fef0665 100644 --- a/src/data-source/models/where/where-filter-keys.model.ts +++ b/src/data-source/models/where/where-filter-keys.model.ts @@ -68,6 +68,7 @@ const whereFilterKeysRecord: Record = { lesserThan: 'lesserThan', lesserThanEquals: 'lesserThanEquals', iLike: 'iLike', + fuzzyLike: 'fuzzyLike', is: 'is', where: 'where', includes: 'includes', diff --git a/src/data-source/nested-where-filter.test.ts b/src/data-source/nested-where-filter.test.ts index 9a9ff7c..001f113 100644 --- a/src/data-source/nested-where-filter.test.ts +++ b/src/data-source/nested-where-filter.test.ts @@ -338,5 +338,12 @@ describe('Where filters – deeply nested (object → array → object)', () => }); expect(res.map(c => c.id)).toEqual([container2.id]); }); + + it('fuzzyLike on title', async () => { + const res: Container[] = await containerRepo.findAll({ + where: { title: { fuzzyLike: { value: 'Cont', minSimilarity: 30 } } } + }); + expect(res.map(c => c.id)).toEqual([container.id, container2.id]); + }); }); }); \ No newline at end of file diff --git a/src/data-source/where-filter.test.ts b/src/data-source/where-filter.test.ts index d563885..4a35936 100644 --- a/src/data-source/where-filter.test.ts +++ b/src/data-source/where-filter.test.ts @@ -152,6 +152,23 @@ describe('Where filters', () => { expect(res.map(p => p.id)).toEqual([prodA.id]); }); + it('fuzzyLike', async () => { + // eslint-disable-next-line cspell/spellchecker + // "Alpha" and "Beta" are seeded. A fuzzy search for "Alpah" (typo) should still match. + const res: Product[] = await productRepo.findAll({ + // eslint-disable-next-line cspell/spellchecker + where: { name: { fuzzyLike: { value: 'Alpah', minSimilarity: 30 } } } + }); + expect(res.map(p => p.id)).toEqual([prodA.id]); + }); + + it('fuzzyLike with low similarity returns nothing', async () => { + const res: Product[] = await productRepo.findAll({ + where: { name: { fuzzyLike: { value: 'Zzzz', minSimilarity: 90 } } } + }); + expect(res).toHaveLength(0); + }); + it('null', async () => { await productRepo.updateById(prodA.id, { name: null }); // set to null const res: Product[] = await productRepo.findAll({ where: { name: null } }); diff --git a/src/localization/formatting/format-price.function.ts b/src/localization/formatting/format-price.function.ts index 810c761..e10c829 100644 --- a/src/localization/formatting/format-price.function.ts +++ b/src/localization/formatting/format-price.function.ts @@ -4,7 +4,6 @@ import { inject } from '../../di/inject.function'; import { BigNumber } from '../../utilities/number.utilities'; import { CurrencyCode } from '../models/currency-code.model'; import { LanguageCode } from '../models/language-code.model'; -import { LocalizeOptions } from '../models/localize-options.model'; /** * Default implementation for formatting prices. @@ -14,8 +13,8 @@ import { LocalizeOptions } from '../models/localize-options.model'; */ export const formatPrice: FormatPriceFn = ( price: number | BigNumber, - currency: CurrencyCode = inject(ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS).currency, - language: LanguageCode = inject(ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS).language + currency: CurrencyCode = inject(ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS).currency, + language: LanguageCode = inject(ZIBRI_DI_TOKENS.LOCALIZE_OPTIONS).language ) => { const v: number = typeof price === 'number' ? price : price.toNumber(); return v.toLocaleString(language, { style: 'currency', currency }); diff --git a/src/open-api/open-api.service.ts b/src/open-api/open-api.service.ts index 96318f1..ed29c42 100644 --- a/src/open-api/open-api.service.ts +++ b/src/open-api/open-api.service.ts @@ -28,6 +28,7 @@ import { HttpMethod } from '../http/http-method.enum'; import { HttpStatus } from '../http/http-status.enum'; import { KnownHeader } from '../http/known-header.enum'; import { MimeType } from '../http/mime-type.enum'; +import { FormatDateFn } from '../localization/formatting/format-date-fn.model'; import { type LoggerInterface } from '../logging/logger.interface'; import { FileResponse } from '../parsing/form-data/file-response.model'; import { HtmlResponse } from '../parsing/html/html-response.model'; @@ -41,6 +42,7 @@ import { Newable } from '../types/newable.type'; import { FsUtilities, FsPath } from '../utilities/fs.utilities'; import { MetadataUtilities } from '../utilities/metadata.utilities'; import { ObjectUtilities } from '../utilities/object.utilities'; +import { SemVerUtilities } from '../utilities/sem-ver.utilities'; import { SupportedVersionsOptions } from '../versioning/supported-versions-options.model'; import { Version, VersionFile } from '../versioning/version.model'; import { type VersioningServiceInterface } from '../versioning/versioning-service.interface'; @@ -168,11 +170,16 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { versions: 'all', openApi: { useInOpenApi: false }, handler: () => { - const versions: VersionFile[] = this.versioningService.getVersions(); + const formatDate: FormatDateFn = inject(ZIBRI_DI_TOKENS.FORMAT_DATE); + const versions: VersionFile[] = this.versioningService + .getVersions() + .sort((a, b) => SemVerUtilities.compare(a.value, b.value) === 'bigger' ? -1 : 1); // eslint-disable-next-line jsdoc/require-jsdoc const urls: { url: string, name: string }[] = versions.map(v => ({ url: `${this.openApiRoute}/spec/${v.value}`, - name: v.endsAt == undefined ? `${v.value} (latest)` : v.value + name: v.endsAt == undefined + ? `${v.value} (latest)` + : `${v.value} (${formatDate(v.startsAt)} - ${formatDate(v.endsAt)})` })); const latestVersion: VersionFile | undefined = versions.find(v => v.endsAt == undefined); return FileResponse.fromStream({ @@ -420,7 +427,7 @@ export class OpenApiService implements OpenApiServiceInterface, OnAppInit { private async resolveOpenApiPaths(app: ZibriApplication, version: Version): Promise { const res: OpenApiPaths = {}; - for (const controllerClass of app.options.controllers) { + for (const controllerClass of app.options.controllers.sort((a, b) => a.name.localeCompare(b.name))) { const controllerData: ControllerData | undefined = MetadataUtilities.getControllerData(controllerClass); if (!controllerData) { throw new MissingBaseRouteError(controllerClass); From ec368f4a24c97f129dd3a9429bbc5f765f516b80 Mon Sep 17 00:00:00 2001 From: Tim Fabian <43989843+tim-fabian@users.noreply.github.com> Date: Sat, 30 May 2026 16:16:21 +0200 Subject: [PATCH 3/8] Potential fix for pull request finding 'Missing space in string concatenation' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- src/versioning/versioning.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/versioning/versioning.service.ts b/src/versioning/versioning.service.ts index b1bfe01..e72ef36 100644 --- a/src/versioning/versioning.service.ts +++ b/src/versioning/versioning.service.ts @@ -137,7 +137,7 @@ export class VersioningService implements VersioningServiceInterface, AfterAppIn if (!currentLatest) { throw new Error( 'Inconsistent version state: multiple active versions exist, but they all represent the global version.' - + 'Check for duplicate version files.' + + ' Check for duplicate version files.' ); } return currentLatest; From 9c466160df4025764a3cded289d35f8bf1f4aedf Mon Sep 17 00:00:00 2001 From: Tim Fabian <43989843+tim-fabian@users.noreply.github.com> Date: Sat, 30 May 2026 18:09:34 +0200 Subject: [PATCH 4/8] Potential fix for pull request finding 'Useless assignment to local variable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- src/rate-limiting/rate-limiter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rate-limiting/rate-limiter.ts b/src/rate-limiting/rate-limiter.ts index 8a005fd..cf4d6b0 100644 --- a/src/rate-limiting/rate-limiter.ts +++ b/src/rate-limiting/rate-limiter.ts @@ -18,7 +18,7 @@ export class RateLimiter { warn('initialTokens are bigger than max, replacing initialTokens with the value of max'); initialTokens = max; } - this.tokens = max; + this.tokens = initialTokens; this.lastRefill = Date.now(); } From 7a5bf4890d037fea874a8440170ec508873a9f23 Mon Sep 17 00:00:00 2001 From: Tim Fabian Date: Sat, 30 May 2026 18:37:36 +0200 Subject: [PATCH 5/8] fixed package-lock.json --- package-lock.json | 177 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 123 insertions(+), 54 deletions(-) diff --git a/package-lock.json b/package-lock.json index e134faa..3873e2b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -338,7 +338,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -951,7 +950,6 @@ "integrity": "sha512-lf6d+BdMkJIFCxx2FpajLpqVGGyaGUNFU6jhEM6QUPeGuoA5et2kJXrL0NSY2uWLOVyYYc/FPjzlbe8trA9tBQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=20" } @@ -1020,8 +1018,7 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.1.1.tgz", "integrity": "sha512-y/Vgo6qY08e1t9OqR56qjoFLBCpi4QfWMf2qzD1l9omRZwvSMQGRPz4x0bxkkkU4oocMAeztjzCsmLew//c/8w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@cspell/dict-dart": { "version": "2.3.2", @@ -1168,16 +1165,14 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.15.tgz", "integrity": "sha512-GJYnYKoD9fmo2OI0aySEGZOjThnx3upSUvV7mmqUu8oG+mGgzqm82P/f7OqsuvTaInZZwZbo+PwJQd/yHcyFIw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@cspell/dict-html-symbol-entities": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@cspell/dict-java": { "version": "5.0.12", @@ -1375,8 +1370,7 @@ "resolved": "https://registry.npmjs.org/@cspell/dict-typescript/-/dict-typescript-3.2.3.tgz", "integrity": "sha512-zXh1wYsNljQZfWWdSPYwQhpwiuW0KPW1dSd8idjMRvSD0aSvWWHoWlrMsmZeRl4qM4QCEAjua8+cjflm41cQBg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@cspell/dict-vue": { "version": "3.0.5", @@ -1451,6 +1445,7 @@ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -1463,11 +1458,35 @@ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -1684,6 +1703,7 @@ "resolved": "https://registry.npmjs.org/@foliojs-fork/fontkit/-/fontkit-1.9.2.tgz", "integrity": "sha512-IfB5EiIb+GZk+77TRB86AHroVaqfq8JRFlUbz0WEwsInyCG0epX2tCPOy+UfaWPju30DeVoUAXfzWXmhn753KA==", "license": "MIT", + "peer": true, "dependencies": { "@foliojs-fork/restructure": "^2.0.2", "brotli": "^1.2.0", @@ -1700,6 +1720,7 @@ "resolved": "https://registry.npmjs.org/@foliojs-fork/linebreak/-/linebreak-1.1.2.tgz", "integrity": "sha512-ZPohpxxbuKNE0l/5iBJnOAfUaMACwvUIKCvqtWGKIMv1lPYoNjYXRfhi9FeeV9McBkBLxsMFWTVVhHJA8cyzvg==", "license": "MIT", + "peer": true, "dependencies": { "base64-js": "1.3.1", "unicode-trie": "^2.0.0" @@ -1710,6 +1731,7 @@ "resolved": "https://registry.npmjs.org/@foliojs-fork/pdfkit/-/pdfkit-0.15.3.tgz", "integrity": "sha512-Obc0Wmy3bm7BINFVvPhcl2rnSSK61DQrlHU8aXnAqDk9LCjWdUOPwhgD8Ywz5VtuFjRxmVOM/kQ/XLIBjDvltw==", "license": "MIT", + "peer": true, "dependencies": { "@foliojs-fork/fontkit": "^1.9.2", "@foliojs-fork/linebreak": "^1.1.1", @@ -1722,7 +1744,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/@foliojs-fork/restructure/-/restructure-2.0.2.tgz", "integrity": "sha512-59SgoZ3EXbkfSX7b63tsou/SDGzwUEK6MuB5sKqgVK1/XE0fxmpsOb9DQI8LXW3KfGnAjImCGhhEb7uPPAUVNA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@gerrit0/mini-shiki": { "version": "3.23.0", @@ -2559,6 +2582,7 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 20.19.0" }, @@ -2571,6 +2595,7 @@ "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-2.0.2.tgz", "integrity": "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==", "license": "MIT", + "peer": true, "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/url": "^3.0.0", @@ -2585,6 +2610,7 @@ "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-2.0.2.tgz", "integrity": "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==", "license": "MIT", + "peer": true, "dependencies": { "@oozcitak/util": "^10.0.0" }, @@ -2597,6 +2623,7 @@ "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-3.0.0.tgz", "integrity": "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==", "license": "MIT", + "peer": true, "dependencies": { "@oozcitak/infra": "^2.0.2", "@oozcitak/util": "^10.0.0" @@ -2610,6 +2637,7 @@ "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-10.0.0.tgz", "integrity": "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==", "license": "MIT", + "peer": true, "engines": { "node": ">=20.0" } @@ -2868,7 +2896,6 @@ "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.26" @@ -3126,25 +3153,29 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", - "license": "MIT" + "license": "MIT", + "peer": 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==", - "license": "MIT" + "license": "MIT", + "peer": 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==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@tybys/wasm-util": { "version": "0.10.2", @@ -3285,7 +3316,6 @@ "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -3386,7 +3416,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } @@ -3524,6 +3553,7 @@ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -3590,7 +3620,6 @@ "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.2", "@typescript-eslint/types": "8.58.2", @@ -3698,7 +3727,6 @@ "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -3780,7 +3808,6 @@ "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.58.2", @@ -4181,7 +4208,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4204,6 +4230,7 @@ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "license": "MIT", + "peer": true, "dependencies": { "acorn": "^8.11.0" }, @@ -4216,6 +4243,7 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "4" }, @@ -4494,7 +4522,8 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/argparse": { "version": "2.0.1", @@ -4686,7 +4715,8 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -4964,6 +4994,7 @@ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "license": "MIT", + "peer": true, "engines": { "node": "^4.5.0 || >= 5.9" } @@ -5103,6 +5134,7 @@ "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.1.2" } @@ -5127,7 +5159,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -5203,7 +5234,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/buffer-from": { "version": "1.1.2", @@ -5541,6 +5573,7 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.8" } @@ -5586,6 +5619,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", + "peer": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -5684,6 +5718,7 @@ "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", + "peer": true, "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.6" @@ -5696,7 +5731,8 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cookie-signature": { "version": "1.2.2", @@ -5791,7 +5827,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cross-spawn": { "version": "7.0.6", @@ -5811,7 +5848,8 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/cspell-config-lib": { "version": "9.6.4", @@ -6043,6 +6081,7 @@ "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", "license": "MIT", + "peer": true, "dependencies": { "is-arguments": "^1.1.1", "is-date-object": "^1.0.5", @@ -6114,6 +6153,7 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.4.0" } @@ -6141,13 +6181,15 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/diff": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.3.1" } @@ -6319,6 +6361,7 @@ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "safe-buffer": "^5.0.1" } @@ -6379,6 +6422,7 @@ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.8.tgz", "integrity": "sha512-2agL3ueZhqxoVrfmntO8yuVj+uNSlIOnhykYHk3Cq0ShYPdUjjUiSJrQvXjq01I9jAuI0Zl2YO8Evv5Mqytm5g==", "license": "MIT", + "peer": true, "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", @@ -6423,6 +6467,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", + "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -6436,6 +6481,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -6445,6 +6491,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -6457,6 +6504,7 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -6685,7 +6733,6 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6746,7 +6793,6 @@ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -6845,7 +6891,6 @@ "integrity": "sha512-CVskZnF38IIxVVlKWi1VCz7YH/gHMJu2IY9bD1AVoBBGIe0xA4FRXJkW2Y+EDs9vQqZTkZZljhK5gL65Ro1PeQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@angular-eslint/bundled-angular-compiler": "20.7.0", "eslint-scope": "^9.0.0" @@ -6984,7 +7029,6 @@ "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", @@ -7092,7 +7136,6 @@ "integrity": "sha512-DBsLPs3GsWhX5HylbP9HNG15U0bnwut55Lx12bHB9MpXxQ+R5GC8MwQe+N1UFXxAeQDvEsEDY6ZYwX03K7Z6HQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -7164,7 +7207,6 @@ "integrity": "sha512-RZ3Qsmi2nFGsS+n+kjLAYDPVlrzf7UhTffrDIKr+h2yzAlYP/y5ZulU0yeDEPItos2Ph46JAL5P/On3pe7kDIQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.56.0", @@ -8326,6 +8368,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=4.0" }, @@ -8383,6 +8426,7 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", + "peer": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -8399,6 +8443,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -8408,6 +8453,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -8998,6 +9044,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", + "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -9213,6 +9260,7 @@ "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "license": "MIT", + "peer": true, "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -9819,7 +9867,6 @@ "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "30.4.2", "@jest/types": "30.4.1", @@ -10543,7 +10590,8 @@ "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz", "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==", "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-tokens": { "version": "4.0.0", @@ -10641,7 +10689,6 @@ "integrity": "sha512-75EA7EWZExL/j+MDKQrRbdzcRI2HOkRlmUw8fZJc1ioqFEOvBsq7Rt+A6yCxOt9w/TYNpkt52gC6nm/g5tFIng==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^5.0.0", @@ -10728,6 +10775,7 @@ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", + "peer": true, "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -10739,6 +10787,7 @@ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", + "peer": true, "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" @@ -10942,37 +10991,43 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isinteger": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isnumber": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isstring": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.kebabcase": { "version": "4.1.1", @@ -10999,7 +11054,8 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.snakecase": { "version": "4.1.1", @@ -11811,6 +11867,7 @@ "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "license": "MIT", + "peer": true, "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" @@ -12113,7 +12170,8 @@ "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/parent-module": { "version": "2.0.0", @@ -12278,7 +12336,6 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.21.0.tgz", "integrity": "sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==", "license": "MIT", - "peer": true, "dependencies": { "pg-connection-string": "^2.13.0", "pg-pool": "^3.14.0", @@ -12498,7 +12555,8 @@ "node_modules/png-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", - "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==", + "peer": true }, "node_modules/possible-typed-array-names": { "version": "1.1.0", @@ -12585,7 +12643,6 @@ "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -12741,6 +12798,7 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -13174,7 +13232,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -13265,6 +13322,7 @@ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", "license": "BlueOak-1.0.0", + "peer": true, "engines": { "node": ">=11.0.0" } @@ -13609,6 +13667,7 @@ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", @@ -13627,6 +13686,7 @@ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.7.tgz", "integrity": "sha512-e0LyK91f3cUxTmv95/KzoLg47+zF+s/sbxRGDNsyG4dmIP8ZSX8ax6byOxfJXeNNtS/8AZlfD+uP7gBeR7DLlg==", "license": "MIT", + "peer": true, "dependencies": { "debug": "~4.4.1", "ws": "~8.20.1" @@ -13666,6 +13726,7 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", + "peer": true, "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -13679,6 +13740,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -13688,6 +13750,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", + "peer": true, "dependencies": { "mime-db": "1.52.0" }, @@ -13700,6 +13763,7 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -14459,7 +14523,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tinyglobby": { "version": "0.2.16", @@ -15090,7 +15155,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15183,6 +15247,7 @@ "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.3.0", "unicode-trie": "^2.0.0" @@ -15193,6 +15258,7 @@ "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", "license": "MIT", + "peer": true, "dependencies": { "pako": "^0.2.5", "tiny-inflate": "^1.0.0" @@ -15310,7 +15376,8 @@ "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==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/v8-to-istanbul": { "version": "9.3.0", @@ -15722,6 +15789,7 @@ "resolved": "https://registry.npmjs.org/xmldoc/-/xmldoc-2.0.3.tgz", "integrity": "sha512-6gRk4NY/Jvg67xn7OzJuxLRsGgiXBaPUQplVJ/9l99uIugxh4FTOewYz5ic8WScj7Xx/2WvhENiQKwkK9RpE4w==", "license": "MIT", + "peer": true, "dependencies": { "sax": "^1.4.3" }, @@ -15852,6 +15920,7 @@ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=6" } From 70563757ed37f849abdb21b5217f14db9d9aa36d Mon Sep 17 00:00:00 2001 From: Tim Fabian Date: Sat, 30 May 2026 19:31:00 +0200 Subject: [PATCH 6/8] fixed versions polluting root dir on tests --- src/__testing__/test-server/providers.ts | 26 +++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/__testing__/test-server/providers.ts b/src/__testing__/test-server/providers.ts index ca10653..f3a864b 100644 --- a/src/__testing__/test-server/providers.ts +++ b/src/__testing__/test-server/providers.ts @@ -3,10 +3,30 @@ import { randomBytes } from 'node:crypto'; import { AesGcmEncryptionStrategy } from '../../auth/encryption/strategies/aes-gcm.encryption-strategy'; import { PasswordResetEmailTemplate } from '../../auth/strategies/jwt/jwt-auth.controller'; import { CronServiceInterface } from '../../cron/cron-service.interface'; +import { Inject } from '../../di/decorators/inject.decorator'; import { ZIBRI_DI_TOKENS } from '../../di/default/zibri-di-tokens.default'; import { defineProvider, DiProvider } from '../../di/models/di-provider.model'; +import { type Header } from '../../http/header.type'; import { MultithreadingServiceInterface } from '../../multithreading/services/multithreading-service.interface'; -import { noOp, noOpAsync } from '../constants'; +import { type RouterInterface } from '../../routing/router.interface'; +import { FsPath, FsUtilities } from '../../utilities/fs.utilities'; +import { VersioningService } from '../../versioning/versioning.service'; +import { noOp, noOpAsync, testFileFolder } from '../constants'; + +const testVersionsDir: FsPath = FsUtilities.getPath(testFileFolder, 'versions-default'); + +class TestVersioningService extends VersioningService { + constructor( + @Inject(ZIBRI_DI_TOKENS.VERSION_HEADER) + versionHeader: Header, + @Inject(ZIBRI_DI_TOKENS.ROUTER) + router: RouterInterface + ) { + super(versionHeader, router); + // eslint-disable-next-line typescript/no-unsafe-member-access, typescript/no-explicit-any + (this as any).versionsPath = testVersionsDir; + } +} export const defaultTestServerProviders: DiProvider[] = [ defineProvider({ @@ -83,5 +103,9 @@ export const defaultTestServerProviders: DiProvider[] = [ }; return res; } + }), + defineProvider({ + token: ZIBRI_DI_TOKENS.VERSIONING_SERVICE, + useClass: TestVersioningService }) ]; \ No newline at end of file From 72c46e3c8f0957373753b28c353726582f33ff28 Mon Sep 17 00:00:00 2001 From: Tim Fabian Date: Sat, 30 May 2026 20:56:32 +0200 Subject: [PATCH 7/8] improved test logging --- .../test-server/start-test-server.function.ts | 9 ++++----- src/versioning/versioning-websocket.test.ts | 10 ++++++++-- src/versioning/versioning.service.test.ts | 10 ++++++++-- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/__testing__/test-server/start-test-server.function.ts b/src/__testing__/test-server/start-test-server.function.ts index 4e1e870..1d1e6d5 100644 --- a/src/__testing__/test-server/start-test-server.function.ts +++ b/src/__testing__/test-server/start-test-server.function.ts @@ -74,17 +74,16 @@ export class StartedTestServer { version = this.app.options.version }: StartTestServerOptions = {} ): Promise { + const logger: LoggerInterface = inject(ZIBRI_DI_TOKENS.LOGGER); + await logger.info('re initializes test server...'); + const info: typeof logger.info = logger.info; + logger.info = noOp; await this.app.shutdown(); // Reset singleton — every test file gets a clean container with no stale instances. DiContainer['singleton'] = undefined; GlobalRegistry['appData'].state = AppState.OFFLINE; - const logger: LoggerInterface = inject(ZIBRI_DI_TOKENS.LOGGER); - await logger.info('re initializes test server...'); - const info: typeof logger.info = logger.info; - logger.info = noOp; - this.app = new ZibriApplication({ name: 'test', version, diff --git a/src/versioning/versioning-websocket.test.ts b/src/versioning/versioning-websocket.test.ts index fc09283..e0705ae 100644 --- a/src/versioning/versioning-websocket.test.ts +++ b/src/versioning/versioning-websocket.test.ts @@ -93,8 +93,14 @@ async function writeVersionFile(file: VersionFile): Promise { } async function restartServer(options: { version?: SemVerVersion, websocketControllers?: Newable[] } = {}): Promise { - await server.reInit(options); - baseUrl = await server.start(); + try { + await server.reInit(options); + baseUrl = await server.start(); + } + catch (error) { + console.error(error); + throw error; + } } async function sendWsEvent( diff --git a/src/versioning/versioning.service.test.ts b/src/versioning/versioning.service.test.ts index 1e2a624..54fddce 100644 --- a/src/versioning/versioning.service.test.ts +++ b/src/versioning/versioning.service.test.ts @@ -111,8 +111,14 @@ async function readVersionFile(version: string): Promise { // Since version files are read only during afterAppInit, which runs once at startup, // we need to restart the server for each test scenario. We'll use a helper. async function restartServer(options: { version?: SemVerVersion, controllers?: Newable[] } = {}): Promise { - await server.reInit(options); - baseUrl = await server.start(); + try { + await server.reInit(options); + baseUrl = await server.start(); + } + catch (error) { + console.error(error); + throw error; + } } beforeAll(async () => { From 281dab31c3cd7a0e196a8048a9bcd4f0990a1446 Mon Sep 17 00:00:00 2001 From: Tim Fabian Date: Sat, 30 May 2026 21:29:30 +0200 Subject: [PATCH 8/8] container port reapply fix --- .../test-server/start-test-server.function.ts | 17 ++++++++++++++++- src/data-source/nested-where-filter.test.ts | 2 +- src/versioning/versioning-websocket.test.ts | 10 ++-------- src/versioning/versioning.service.test.ts | 10 ++-------- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/__testing__/test-server/start-test-server.function.ts b/src/__testing__/test-server/start-test-server.function.ts index 1d1e6d5..5070f68 100644 --- a/src/__testing__/test-server/start-test-server.function.ts +++ b/src/__testing__/test-server/start-test-server.function.ts @@ -39,11 +39,24 @@ type StartTestServerOptions = Partial< }; export class StartedTestServer { + private readonly containerPorts: Map, number>; + constructor( private app: ZibriApplication, private readonly containers: AbstractStartedContainer[], private readonly exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => undefined as never) - ) {} + ) { + this.containerPorts = new Map( + app.options.dataSources.map((ds, i) => [ds as Newable, containers[i].getMappedPort(5432)]) + ); + } + + private reApplyContainerPorts(): void { + for (const [ds, port] of this.containerPorts) { + const dataSource: PostgresDataSource = inject(ds); + (dataSource.options as OmitStrict) = { ...dataSource.options, port }; + } + } async start(): Promise { await this.app.start(0); @@ -84,6 +97,8 @@ export class StartedTestServer { DiContainer['singleton'] = undefined; GlobalRegistry['appData'].state = AppState.OFFLINE; + this.reApplyContainerPorts(); + this.app = new ZibriApplication({ name: 'test', version, diff --git a/src/data-source/nested-where-filter.test.ts b/src/data-source/nested-where-filter.test.ts index 001f113..73557f2 100644 --- a/src/data-source/nested-where-filter.test.ts +++ b/src/data-source/nested-where-filter.test.ts @@ -343,7 +343,7 @@ describe('Where filters – deeply nested (object → array → object)', () => const res: Container[] = await containerRepo.findAll({ where: { title: { fuzzyLike: { value: 'Cont', minSimilarity: 30 } } } }); - expect(res.map(c => c.id)).toEqual([container.id, container2.id]); + expect(res.map(c => c.id)).toEqual(expect.arrayContaining([container.id, container2.id])); }); }); }); \ No newline at end of file diff --git a/src/versioning/versioning-websocket.test.ts b/src/versioning/versioning-websocket.test.ts index e0705ae..fc09283 100644 --- a/src/versioning/versioning-websocket.test.ts +++ b/src/versioning/versioning-websocket.test.ts @@ -93,14 +93,8 @@ async function writeVersionFile(file: VersionFile): Promise { } async function restartServer(options: { version?: SemVerVersion, websocketControllers?: Newable[] } = {}): Promise { - try { - await server.reInit(options); - baseUrl = await server.start(); - } - catch (error) { - console.error(error); - throw error; - } + await server.reInit(options); + baseUrl = await server.start(); } async function sendWsEvent( diff --git a/src/versioning/versioning.service.test.ts b/src/versioning/versioning.service.test.ts index 54fddce..1e2a624 100644 --- a/src/versioning/versioning.service.test.ts +++ b/src/versioning/versioning.service.test.ts @@ -111,14 +111,8 @@ async function readVersionFile(version: string): Promise { // Since version files are read only during afterAppInit, which runs once at startup, // we need to restart the server for each test scenario. We'll use a helper. async function restartServer(options: { version?: SemVerVersion, controllers?: Newable[] } = {}): Promise { - try { - await server.reInit(options); - baseUrl = await server.start(); - } - catch (error) { - console.error(error); - throw error; - } + await server.reInit(options); + baseUrl = await server.start(); } beforeAll(async () => {