From 22651f0e212c183ec13a3cd47493eefa2dc60d7f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 04:34:14 +0000 Subject: [PATCH 1/2] Initial plan From 8dceedd05bcb6a071d74fc85453e3cabafecb6d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 14 Sep 2025 04:43:08 +0000 Subject: [PATCH 2/2] Fix npm lint issues by formatting code and resolving ESLint errors --- .eslintrc.json | 19 +- package-lock.json | 3413 +++-------------- package.json | 8 +- src/app.ts | 40 +- src/env.ts | 2 +- src/localserver.ts | 4 +- src/middleware/admin.ts | 42 +- src/middleware/auth.ts | 45 +- src/middleware/cors.ts | 12 +- src/middleware/errors.ts | 76 +- src/middleware/healthcheck.ts | 6 +- src/middleware/index.ts | 12 +- src/middleware/webhook.ts | 30 +- src/modules/auth/auth.controller.ts | 44 +- src/modules/auth/auth.interface.ts | 2 +- src/modules/auth/auth.service.test.ts | 97 +- src/modules/auth/auth.service.ts | 234 +- src/modules/auth/auth.types.ts | 2 +- src/modules/auth/dto/login.dto.ts | 10 +- src/modules/auth/dto/refresh.dto.ts | 4 +- src/modules/auth/dto/register.dto.ts | 28 +- src/modules/auth/dto/wslogin.dto.ts | 6 +- src/modules/auth/firebase/index.ts | 37 +- src/modules/auth/index.ts | 8 +- src/modules/billing/billing.controller.ts | 38 +- src/modules/billing/billing.interface.ts | 2 +- src/modules/billing/billing.service.ts | 265 +- .../billing/dto/generate-payment-link.dto.ts | 10 +- src/modules/billing/index.ts | 2 +- src/modules/blockscan/etherscan/index.ts | 29 +- src/modules/blockscan/index.ts | 26 +- .../blockscan/proxy-detection/index.ts | 73 +- src/modules/blockscan/supported-chains.ts | 207 +- src/modules/blockscan/throttler.ts | 11 +- .../configuration/config.service.test.ts | 104 +- src/modules/configuration/config.service.ts | 30 +- src/modules/configuration/error.ts | 2 +- src/modules/configuration/index.ts | 36 +- src/modules/configuration/mapping.ts | 52 +- src/modules/configuration/util.test.ts | 112 +- src/modules/configuration/util.ts | 16 +- src/modules/connection/dbcontext.ts | 34 +- src/modules/connection/index.ts | 2 +- src/modules/contract/contract.controller.ts | 29 +- src/modules/contract/contract.service.test.ts | 54 +- src/modules/contract/contract.service.ts | 47 +- src/modules/contract/index.ts | 2 +- src/modules/coupon/coupon.controller.ts | 69 +- src/modules/coupon/coupon.interface.ts | 2 +- src/modules/coupon/coupon.service.ts | 63 +- src/modules/coupon/dto/index.ts | 4 +- .../coupon/dto/update-coupon-status.dto.ts | 2 +- src/modules/coupon/index.ts | 2 +- src/modules/cron/billing.cron.ts | 83 +- src/modules/cron/index.ts | 2 +- src/modules/errors/ApplicationError.ts | 22 +- src/modules/errors/InvalidRequestError.ts | 6 +- src/modules/errors/UnauthenticatedError.ts | 8 +- src/modules/errors/index.ts | 2 +- src/modules/http/base/base-http-client.ts | 28 +- src/modules/http/base/errors.ts | 2 +- src/modules/http/base/index.ts | 2 +- src/modules/http/index.ts | 2 +- src/modules/http/paddle-http-client/client.ts | 161 +- src/modules/http/paddle-http-client/index.ts | 2 +- src/modules/http/paddle-http-client/types.ts | 5 +- src/modules/http/stripe-http-client/client.ts | 67 +- src/modules/http/stripe-http-client/index.ts | 4 +- .../http/tenderly-http-client/client.ts | 69 +- .../http/tenderly-http-client/index.ts | 4 +- .../http/tenderly-http-client/types.ts | 2 +- src/modules/index.ts | 18 +- src/modules/mailing/index.ts | 6 +- src/modules/mailing/mailgun.ts | 58 +- src/modules/mailing/types.ts | 8 +- .../dto/accept-project-invitation.dto.ts | 4 +- .../project/dto/cancel-project-invite.dto.ts | 6 +- .../dto/create-project-collection.dto.ts | 10 +- src/modules/project/dto/create-project.dto.ts | 23 +- .../dto/generate-project-api-key.dto.ts | 12 +- src/modules/project/dto/index.ts | 16 +- .../project/dto/invite-project-user.dto.ts | 10 +- .../project/dto/register-device.dto.ts | 6 +- .../dto/update-project-collection.dto.ts | 8 +- .../dto/update-project-user-permission.dto.ts | 2 +- src/modules/project/index.ts | 2 +- src/modules/project/project.controller.ts | 243 +- src/modules/project/project.interface.ts | 6 +- src/modules/project/project.service.test.ts | 675 ++-- src/modules/project/project.service.ts | 295 +- .../billing/billing.repository.ts | 189 +- src/modules/repositories/billing/index.ts | 4 +- src/modules/repositories/billing/queries.ts | 12 +- src/modules/repositories/billing/types.ts | 7 +- .../repositories/coupon/coupon.repository.ts | 107 +- src/modules/repositories/coupon/index.ts | 4 +- src/modules/repositories/coupon/queries.ts | 22 +- src/modules/repositories/coupon/types.ts | 4 +- .../evm-contracts.repository.test.ts | 60 +- .../evm-contracts/evm-contracts.repository.ts | 23 +- .../repositories/evm-contracts/index.ts | 4 +- src/modules/repositories/index.ts | 4 +- src/modules/repositories/projects/index.ts | 4 +- .../projects/project.repository.test.ts | 459 ++- .../projects/project.repository.ts | 157 +- src/modules/repositories/projects/queries.ts | 14 +- src/modules/repositories/projects/types.ts | 10 +- src/modules/repositories/projects/util.ts | 18 +- src/modules/repositories/sample/index.ts | 4 +- .../sample/sample.repository.test.ts | 26 +- .../repositories/sample/sample.repository.ts | 16 +- src/modules/repositories/users/index.ts | 4 +- src/modules/repositories/users/queries.ts | 9 +- .../users/user.repository.test.ts | 282 +- .../repositories/users/user.repository.ts | 56 +- src/modules/shared/batching.ts | 16 +- src/modules/shared/index.ts | 8 +- src/modules/shared/types/index.ts | 2 +- src/modules/shared/util.test.ts | 16 +- src/modules/shared/util.ts | 5 +- src/modules/storage/index.ts | 49 +- src/modules/subscription/index.ts | 2 +- .../subscription/subscription.controller.ts | 32 +- .../subscription/subscription.interface.ts | 2 +- .../subscription/subscription.service.test.ts | 22 +- .../subscription/subscription.service.ts | 21 +- src/modules/tenderly/index.ts | 2 +- src/modules/tenderly/tenderly.controller.ts | 24 +- src/modules/tenderly/tenderly.interface.ts | 2 +- src/modules/tenderly/tenderly.service.ts | 43 +- src/server.ts | 38 +- src/test/util.ts | 49 +- src/types/index.d.ts | 14 +- 133 files changed, 3891 insertions(+), 5340 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index bab19fe..897f895 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,18 @@ { - "extends": [ - "@bonadocs" - ] + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/no-explicit-any": "warn", + "prefer-const": "error", + "no-var": "error" + }, + "parserOptions": { + "ecmaVersion": 2020, + "sourceType": "module" + }, + "env": { + "node": true, + "es6": true + } } diff --git a/package-lock.json b/package-lock.json index 173fa93..880481f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,6 @@ "version": "1.0.0", "license": "Bonadocs Proprietary", "dependencies": { - "@bonadocs/di": "^1.0.0", - "@bonadocs/logger": "^1.0.0", "@paddle/paddle-node-sdk": "^2.7.0", "axios": "^1.7.9", "body-parser": "^1.20.3", @@ -35,7 +33,6 @@ "typedi": "^0.10.0" }, "devDependencies": { - "@bonadocs/eslint-config": "latest", "@jest/globals": "^29.7.0", "@types/cors": "^2.8.17", "@types/node": "^22.5.4", @@ -44,8 +41,11 @@ "@types/randomstring": "^1.3.0", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.7", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "class-validator-jsonschema": "^5.0.1", "dotenv": "^16.4.5", + "eslint": "^8.57.1", "husky": "^9.1.7", "jest": "^29.7.0", "prettier": "^3.4.2", @@ -64,20 +64,6 @@ "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", "license": "MIT" }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", @@ -138,9 +124,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", + "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", "dev": true, "license": "MIT", "engines": { @@ -148,22 +134,22 @@ } }, "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", + "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", "dependencies": { - "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", + "@babel/traverse": "^7.28.4", + "@babel/types": "^7.28.4", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -189,16 +175,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -232,6 +218,16 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", @@ -247,15 +243,15 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -305,27 +301,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", - "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", + "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", - "@babel/types": "^7.27.6" + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", + "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.28.4" }, "bin": { "parser": "bin/babel-parser.js" @@ -589,38 +585,28 @@ } }, "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", + "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", + "@babel/generator": "^7.28.3", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.4", "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/types": "^7.28.4", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", + "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -638,57 +624,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@bonadocs/di": { - "version": "1.0.0", - "resolved": "https://npm.pkg.github.com/download/@bonadocs/di/1.0.0/605970fc41db82cd3086cee9721f98e45ad54212", - "integrity": "sha512-zi+zNGXeDhcoaDGs9ZnyIaIQEJomQrBgPVZHOUfk/sV9PZor4nDZdOnSwbMAcdGqkmigRd4Bs0pModnX+ykJMw==", - "engines": { - "node": ">=20" - }, - "peerDependencies": { - "@bonadocs/logger": "1.x", - "express": "4.x", - "reflect-metadata": "^0.2.x", - "routing-controllers": "^0.10.x", - "typedi": "^0.10.x" - } - }, - "node_modules/@bonadocs/eslint-config": { - "version": "1.0.1", - "resolved": "https://npm.pkg.github.com/download/@bonadocs/eslint-config/1.0.1/ed7e515a9173aaaa7db02265f677dc527dd2b36b", - "integrity": "sha512-8c4IHKt3dgpMWVVH9QVHS4cy8ahjd3bFe6vJ3Rw8mFF9dMOk7mCSE8utuKUzB5IbgHvvhb3GTlwSZA6eSHCD4g==", - "dev": true, - "dependencies": { - "@bonadocs/prettier-config": "latest", - "@typescript-eslint/eslint-plugin": "^8.2.0", - "@typescript-eslint/parser": "^8.2.0", - "eslint": "^8.57.1", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-import": "^2.29.1", - "eslint-plugin-n": "^17.10.2", - "eslint-plugin-promise": "^7.1.0", - "npm-run-all": "^4.1.5", - "prettier": "^3.3.3" - } - }, - "node_modules/@bonadocs/logger": { - "version": "1.0.0", - "resolved": "https://npm.pkg.github.com/download/@bonadocs/logger/1.0.0/49a021fa21f9f5e503609c757caeca5cefa9dc22", - "integrity": "sha512-Vq0pzhoqXPkNY9EE/10NqPGzPrbZEPQhv5p5+G7Gd5eC7EXmtuz1ixiDnbPlvAyp0Vs7hnspx98e9d1uBqskCw==", - "engines": { - "node": ">=18" - } - }, - "node_modules/@bonadocs/prettier-config": { - "version": "1.0.1", - "resolved": "https://npm.pkg.github.com/download/@bonadocs/prettier-config/1.0.1/288b7c64bde194285953e3102994108ea45d856f", - "integrity": "sha512-AGMzpkmLgJdopSemiXjrT5sVr6S8bbTDmmvUUKvImUjrZTl3v5aj4mP155Rc5uUQpqxKY+DPyFhRrGJj2T+HGQ==", - "dev": true, - "dependencies": { - "prettier": "^3.4.2" - } - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -714,9 +649,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", - "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dev": true, "license": "MIT", "dependencies": { @@ -777,16 +712,6 @@ "concat-map": "0.0.1" } }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -811,9 +736,9 @@ } }, "node_modules/@fastify/busboy": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", - "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.2.0.tgz", + "integrity": "sha512-m9FVDXU3GT2ITSe0UaMA5rU3QkfC/UXtCU8y0gSN/GugTqtVldOBWIB5V6V3sbmenVZUIpU6f+mPEO2+m5iTaA==", "license": "MIT" }, "node_modules/@firebase/app-check-interop-types": { @@ -835,92 +760,92 @@ "license": "Apache-2.0" }, "node_modules/@firebase/component": { - "version": "0.6.17", - "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.17.tgz", - "integrity": "sha512-M6DOg7OySrKEFS8kxA3MU5/xc37fiOpKPMz6cTsMUcsuKB6CiZxxNAvgFta8HGRgEpZbi8WjGIj6Uf+TpOhyzg==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.0.tgz", + "integrity": "sha512-wR9En2A+WESUHexjmRHkqtaVH94WLNKt6rmeqZhSLBybg4Wyf0Umk04SZsS6sBq4102ZsDBFwoqMqJYj2IoDSg==", "license": "Apache-2.0", "dependencies": { - "@firebase/util": "1.12.0", + "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@firebase/database": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.19.tgz", - "integrity": "sha512-khE+MIYK+XlIndVn/7mAQ9F1fwG5JHrGKaG72hblCC6JAlUBDd3SirICH6SMCf2PQ0iYkruTECth+cRhauacyQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.0.tgz", + "integrity": "sha512-gM6MJFae3pTyNLoc9VcJNuaUDej0ctdjn3cVtILo3D5lpp0dmUHHLFN/pUKe7ImyeB1KAvRlEYxvIHNF04Filg==", "license": "Apache-2.0", "dependencies": { "@firebase/app-check-interop-types": "0.3.3", "@firebase/auth-interop-types": "0.2.4", - "@firebase/component": "0.6.17", - "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/component": "0.7.0", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", "faye-websocket": "0.11.4", "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@firebase/database-compat": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.10.tgz", - "integrity": "sha512-3sjl6oGaDDYJw/Ny0E5bO6v+KM3KoD4Qo/sAfHGdRFmcJ4QnfxOX9RbG9+ce/evI3m64mkPr24LlmTDduqMpog==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.0.tgz", + "integrity": "sha512-8nYc43RqxScsePVd1qe1xxvWNf0OBnbwHxmXJ7MHSuuTVYFO3eLyLW3PiCKJ9fHnmIz4p4LbieXwz+qtr9PZDg==", "license": "Apache-2.0", "dependencies": { - "@firebase/component": "0.6.17", - "@firebase/database": "1.0.19", - "@firebase/database-types": "1.0.14", - "@firebase/logger": "0.4.4", - "@firebase/util": "1.12.0", + "@firebase/component": "0.7.0", + "@firebase/database": "1.1.0", + "@firebase/database-types": "1.0.16", + "@firebase/logger": "0.5.0", + "@firebase/util": "1.13.0", "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@firebase/database-types": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.14.tgz", - "integrity": "sha512-8a0Q1GrxM0akgF0RiQHliinhmZd+UQPrxEmUv7MnQBYfVFiLtKOgs3g6ghRt/WEGJHyQNslZ+0PocIwNfoDwKw==", + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.16.tgz", + "integrity": "sha512-xkQLQfU5De7+SPhEGAXFBnDryUWhhlFXelEg2YeZOQMCdoe7dL64DDAd77SQsR+6uoXIZY5MB4y/inCs4GTfcw==", "license": "Apache-2.0", "dependencies": { "@firebase/app-types": "0.9.3", - "@firebase/util": "1.12.0" + "@firebase/util": "1.13.0" } }, "node_modules/@firebase/logger": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", - "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz", + "integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@firebase/util": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.0.tgz", - "integrity": "sha512-Z4rK23xBCwgKDqmzGVMef+Vb4xso2j5Q8OG0vVL4m4fA5ZjPMYQazu8OJJC3vtQRC3SQ/Pgx/6TPNVsCd70QRw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.13.0.tgz", + "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@google-cloud/firestore": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.1.tgz", - "integrity": "sha512-ZxOdH8Wr01hBDvKCQfMWqwUcfNcN3JY19k1LtS1fTFhEyorYPLsbWN+VxIRL46pOYGHTPkU3Or5HbT/SLQM5nA==", + "version": "7.11.3", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.3.tgz", + "integrity": "sha512-qsM3/WHpawF07SRVvEJJVRwhYzM7o9qtuksyuqnrMig6fxIrwWnsezECWsG/D5TyYru51Fv5c/RTqNDQ2yU+4w==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -969,9 +894,9 @@ } }, "node_modules/@google-cloud/storage": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.16.0.tgz", - "integrity": "sha512-7/5LRgykyOfQENcm6hDKP8SX/u9XxE5YOiWOkgkwcoO+cG8xT/cyOvp9wwN3IxfdYgpHs8CE7Nq2PKX2lNaEXw==", + "version": "7.17.1", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.17.1.tgz", + "integrity": "sha512-2FMQbpU7qK+OtBPaegC6n+XevgZksobUGo6mGKnXNmeZpvLiAo1gTAE3oTKsrMGDV4VtL8Zzpono0YsK/Q7Iqg==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -1140,9 +1065,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -1152,9 +1077,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" @@ -1187,9 +1112,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" @@ -1628,34 +1553,31 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6.0.0" + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { @@ -1663,16 +1585,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1807,9 +1729,9 @@ } }, "node_modules/@paddle/paddle-node-sdk": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@paddle/paddle-node-sdk/-/paddle-node-sdk-2.7.3.tgz", - "integrity": "sha512-xnT3+dHPbYUI9zfHxAniYNYg/GDUtSupB9HvxELUkfTXhRZrVEALoWmYJ8ZKjJCBdCPED9R6UrH6BUvMNeTsUQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@paddle/paddle-node-sdk/-/paddle-node-sdk-2.8.0.tgz", + "integrity": "sha512-7SeFdE1k8j4Lme0A4SvPh3ak2e4HLtmTwsxEcQeWWSwy3vcwMSoE8hpHKGkqgHWdLV71FKFCMn9RYJFtbUb4Zg==", "license": "Apache-2.0", "engines": { "node": ">=18" @@ -1899,13 +1821,6 @@ "license": "BSD-3-Clause", "optional": true }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, "node_modules/@scarf/scarf": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", @@ -2035,13 +1950,13 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/body-parser": { @@ -2093,9 +2008,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", - "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.7.tgz", + "integrity": "sha512-R+33OsgWw7rOhD1emjU7dzCDHucJrgJXMA5PYCzJxVil0dsyx5iBEPHqpPfiKNJQb7lZ1vxwoLR4Z87bBUpeGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2160,17 +2075,10 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", - "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "license": "MIT", "dependencies": { "@types/ms": "*", @@ -2197,9 +2105,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.15.31", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.31.tgz", - "integrity": "sha512-jnVe5ULKl6tijxUhvQeNbQG/84fHfg+yMak02cT8QVhBx/F05rAVxCGBYYTh2EKz22D6JF5ktXuNwdx7b9iEGw==", + "version": "22.18.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.18.3.tgz", + "integrity": "sha512-gTVM8js2twdtqM+AE2PdGEe9zGQY4UvmFjan9rZcVb6FGdStfjWoWejdmy4CfWVO9rh5MiYQGZloKAGkJt8lMw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -2213,9 +2121,9 @@ "license": "MIT" }, "node_modules/@types/pg": { - "version": "8.15.4", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.4.tgz", - "integrity": "sha512-I6UNVBAoYbvuWkkU3oosC8yxqH21f4/Jc4DK71JLG3dT2mdlGe1z+ep/LQGXaKaOgcvUrsQoPRqfgtMcvZiJhg==", + "version": "8.15.5", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.5.tgz", + "integrity": "sha512-LF7lF6zWEKxuT3/OR8wAZGzkg4ENGXFNyiV/JeOt9z5B+0ZVwbql9McqX5c/WStFq1GaGso7H1AzP/qSzmlCKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2244,28 +2152,29 @@ "license": "MIT" }, "node_modules/@types/request": { - "version": "2.48.12", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", - "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "version": "2.48.13", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", + "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", "license": "MIT", "optional": true, "dependencies": { "@types/caseless": "*", "@types/node": "*", "@types/tough-cookie": "*", - "form-data": "^2.5.0" + "form-data": "^2.5.5" } }, "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.3.tgz", - "integrity": "sha512-XHIrMD0NpDrNM/Ckf7XJiBbLl57KEhT3+i3yY+eWm+cqYZJQTZrKo8Y8AWKnuV5GT4scfuUGt9LzNoIx3dU1nQ==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "license": "MIT", "optional": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.35", "safe-buffer": "^5.2.1" }, @@ -2273,6 +2182,13 @@ "node": ">= 0.12" } }, + "node_modules/@types/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/send": { "version": "0.17.5", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", @@ -2327,9 +2243,9 @@ "optional": true }, "node_modules/@types/validator": { - "version": "13.15.1", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.1.tgz", - "integrity": "sha512-9gG6ogYcoI2mCMLdcO0NYI0AYrbxIjv0MDmy/5Ywo6CpWWrqYayc+mmgxRsCgtcGJm9BSbXkMsmxGah1iGHAAQ==", + "version": "13.15.3", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.3.tgz", + "integrity": "sha512-7bcUmDyS6PN3EuD9SlGGOxM77F8WLVsrwkxyWxKnxzmXoequ6c7741QBrANq6htVRGOITJ7z72mTP6Z4XyuG+Q==", "license": "MIT" }, "node_modules/@types/yargs": { @@ -2350,149 +2266,124 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", - "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", + "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/type-utils": "8.34.0", - "@typescript-eslint/utils": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/type-utils": "6.21.0", + "@typescript-eslint/utils": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", + "debug": "^4.3.4", "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "ignore": "^5.2.4", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.34.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/parser": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", - "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", + "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", - "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.0", - "@typescript-eslint/types": "^8.34.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "eslint": "^7.0.0 || ^8.0.0" }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", - "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", + "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", - "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", - "dev": true, - "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", - "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", + "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "@typescript-eslint/utils": "6.21.0", "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/types": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", - "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", + "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", @@ -2500,89 +2391,78 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", - "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", + "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/project-service": "8.34.0", - "@typescript-eslint/tsconfig-utils": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/visitor-keys": "6.21.0", "debug": "^4.3.4", - "fast-glob": "^3.3.2", + "globby": "^11.1.0", "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "minimatch": "9.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", - "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", + "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0" + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.21.0", + "@typescript-eslint/types": "6.21.0", + "@typescript-eslint/typescript-estree": "6.21.0", + "semver": "^7.5.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "eslint": "^7.0.0 || ^8.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", - "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", + "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "6.21.0", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^16.0.0 || >=18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -2659,9 +2539,9 @@ "license": "MIT" }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -2771,132 +2651,20 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", - "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-shim-unscopables": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/arrify": { @@ -2909,23 +2677,6 @@ "node": ">=8" } }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -2942,30 +2693,14 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", - "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "node_modules/axios": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.1.tgz", + "integrity": "sha512-Kn4kbSXpkFHCGE6rBFNwIv0GQs4AvDT80jlveJDKFxjbTYMUeB4QtsdPCv6H8Cm19Je7IU6VFtRl2zWZI0rudQ==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -3052,9 +2787,9 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -3075,7 +2810,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { @@ -3121,10 +2856,20 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.3.tgz", + "integrity": "sha512-mcE+Wr2CAhHNWxXN/DdTI+n4gsPc5QpXpWnyCQWiQYIYZX+ZMJ8juXZgjRa/0/YPJo/NSsgW15/YgmI4nbysYw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, "node_modules/bignumber.js": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.0.tgz", - "integrity": "sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", "engines": { "node": "*" @@ -3192,9 +2937,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.26.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", + "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", "dev": true, "funding": [ { @@ -3212,9 +2957,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", + "baseline-browser-mapping": "^2.8.2", + "caniuse-lite": "^1.0.30001741", + "electron-to-chromium": "^1.5.218", + "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": { @@ -3295,25 +3041,6 @@ "node": ">= 6.0.0" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -3370,9 +3097,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001723", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz", - "integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==", + "version": "1.0.30001741", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", + "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", "dev": true, "funding": [ { @@ -3458,9 +3185,9 @@ } }, "node_modules/class-validator-jsonschema": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/class-validator-jsonschema/-/class-validator-jsonschema-5.0.2.tgz", - "integrity": "sha512-b+oHktogJLWf/IRWEoVizrT/0TkYBHKxSLHbE4lq5DEJAEhq6z6nBJbw4hwh9akEeX3v08Hw8d5TBEv7VcZ8TQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/class-validator-jsonschema/-/class-validator-jsonschema-5.1.0.tgz", + "integrity": "sha512-FFOeqLR+Ng+iGoapZksAYwNFMSxTqQaFt32UHFrIDwa8bk72mWMWH5U/LEpvhnQh5ZD1sWZFbh3oTNBcFtt+4A==", "dev": true, "license": "MIT", "dependencies": { @@ -3586,13 +3313,6 @@ "typedarray": "^0.0.6" } }, - "node_modules/concat-stream/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT", - "optional": true - }, "node_modules/concat-stream/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -3626,13 +3346,6 @@ "safe-buffer": "~5.1.0" } }, - "node_modules/confusing-browser-globals": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", - "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", - "dev": true, - "license": "MIT" - }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3760,64 +3473,10 @@ "node": ">= 8" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3832,9 +3491,9 @@ } }, "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", + "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3870,42 +3529,6 @@ "node": ">=0.10.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3971,6 +3594,19 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -3984,9 +3620,9 @@ } }, "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4051,26 +3687,10 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/electron-to-chromium": { - "version": "1.5.167", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz", - "integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==", + "version": "1.5.218", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", + "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", "dev": true, "license": "ISC" }, @@ -4103,29 +3723,15 @@ } }, "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", "license": "MIT", "optional": true, "dependencies": { "once": "^1.4.0" } }, - "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -4136,75 +3742,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -4250,37 +3787,6 @@ "node": ">= 0.4" } }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -4367,411 +3873,82 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-compat-utils": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/eslint-compat-utils/-/eslint-compat-utils-0.5.1.tgz", - "integrity": "sha512-3z3vFexKIEnjHE3zCMRo6fn/e44U7T1khUjg+Hp0ZQMCigh28rALD0nPFBcGZuiLC5rLZa2ubQHDRln09JfU2Q==", + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "semver": "^7.5.4" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "eslint": ">=6.0.0" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-config-airbnb-base": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", - "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", - "dependencies": { - "confusing-browser-globals": "^1.0.10", - "object.assign": "^4.1.2", - "object.entries": "^1.1.5", - "semver": "^6.3.0" - }, + "license": "Apache-2.0", "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "eslint": "^7.32.0 || ^8.2.0", - "eslint-plugin-import": "^2.25.2" - } - }, - "node_modules/eslint-config-airbnb-base/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "ms": "^2.1.1" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/eslint-module-utils": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", - "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "debug": "^3.2.7" + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": ">=4" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es-x": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-es-x/-/eslint-plugin-es-x-7.8.0.tgz", - "integrity": "sha512-7Ds8+wAAoV3T+LAKeu39Y5BzXCrGKrcISfgKEqTS4BDN8SFEDQd0S43jiQ8vIa3wUKD07qitZdfzlenSi8/0qQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/ota-meshi", - "https://opencollective.com/eslint" - ], - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.1.2", - "@eslint-community/regexpp": "^4.11.0", - "eslint-compat-utils": "^0.5.1" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": ">=8" - } - }, - "node_modules/eslint-plugin-eslint-comments": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz", - "integrity": "sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5", - "ignore": "^5.0.5" - }, - "engines": { - "node": ">=6.5.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-eslint-comments/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint-plugin-eslint-comments/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.31.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", - "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rtsao/scc": "^1.1.0", - "array-includes": "^3.1.8", - "array.prototype.findlastindex": "^1.2.5", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.12.0", - "hasown": "^2.0.2", - "is-core-module": "^2.15.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.8", - "object.groupby": "^1.0.3", - "object.values": "^1.2.0", - "semver": "^6.3.1", - "string.prototype.trimend": "^1.0.8", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" - } - }, - "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-n": { - "version": "17.20.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-17.20.0.tgz", - "integrity": "sha512-IRSoatgB/NQJZG5EeTbv/iAx1byOGdbbyhQrNvWdCfTnmPxUT0ao9/eGOeG7ljD8wJBsxwE8f6tES5Db0FRKEw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.5.0", - "@typescript-eslint/utils": "^8.26.1", - "enhanced-resolve": "^5.17.1", - "eslint-plugin-es-x": "^7.8.0", - "get-tsconfig": "^4.8.1", - "globals": "^15.11.0", - "ignore": "^5.3.2", - "minimatch": "^9.0.5", - "semver": "^7.6.3", - "ts-declaration-location": "^1.0.6" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": ">=8.23.0" - } - }, - "node_modules/eslint-plugin-n/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-plugin-n/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint-plugin-promise": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-7.2.1.tgz", - "integrity": "sha512-SWKjd+EuvWkYaS+uN2csvj0KoP43YTu7+phKQ5v+xw6+A0gutVX2yqCeCkC3uLCJFiPfR2dD8Es5L7yUsmvEaA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.9.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -4837,9 +4014,9 @@ } }, "node_modules/ethers": { - "version": "6.14.4", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.14.4.tgz", - "integrity": "sha512-Jm/dzRs2Z9iBrT6e9TvGxyb5YVKAPLlpna7hjxH7KH/++DSh2T/JVmQUv7iHI5E55hDbp/gEVvstWYXVxXFzsA==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz", + "integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==", "funding": [ { "type": "individual", @@ -5008,9 +4185,9 @@ } }, "node_modules/express-session": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", - "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", + "version": "1.18.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", + "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", "license": "MIT", "optional": true, "dependencies": { @@ -5018,7 +4195,7 @@ "cookie-signature": "1.0.7", "debug": "2.6.9", "depd": "~2.0.0", - "on-headers": "~1.0.2", + "on-headers": "~1.1.0", "parseurl": "~1.3.3", "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" @@ -5130,7 +4307,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "devOptional": true, "license": "MIT" }, "node_modules/fast-glob": { @@ -5241,29 +4417,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5328,9 +4481,9 @@ } }, "node_modules/firebase-admin": { - "version": "13.4.0", - "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.4.0.tgz", - "integrity": "sha512-Y8DcyKK+4pl4B93ooiy1G8qvdyRMkcNFfBSh+8rbVcw4cW8dgG0VXCCTp5NUwub8sn9vSPsOwpb9tE2OuFmcfQ==", + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.5.0.tgz", + "integrity": "sha512-QZOpv1DJRJpH8NcWiL1xXE10tw3L/bdPFlgjcWrqU3ufyOJDYfxB1MMtxiVTwxK16NlybQbEM6ciSich2uWEIQ==", "license": "Apache-2.0", "dependencies": { "@fastify/busboy": "^3.0.0", @@ -5338,6 +4491,7 @@ "@firebase/database-types": "^1.0.6", "@types/node": "^22.8.7", "farmhash-modern": "^1.1.0", + "fast-deep-equal": "^3.1.1", "google-auth-library": "^9.14.2", "jsonwebtoken": "^9.0.0", "jwks-rsa": "^3.1.0", @@ -5375,9 +4529,9 @@ "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", "funding": [ { "type": "individual", @@ -5394,22 +4548,6 @@ } } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -5439,9 +4577,9 @@ } }, "node_modules/form-data": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", - "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5509,27 +4647,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", @@ -5537,16 +4654,6 @@ "license": "MIT", "optional": true }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gaxios": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", @@ -5670,37 +4777,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5776,21 +4852,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, "license": "MIT", "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/google-auth-library": { @@ -5896,17 +4976,26 @@ "node": ">=14.0.0" } }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, "engines": { - "node": ">= 0.4" + "node": ">=0.4.7" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "uglify-js": "^3.1.4" } }, "node_modules/has-flag": { @@ -5919,35 +5008,6 @@ "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -5996,13 +5056,6 @@ "node": ">=16.0.0" } }, - "node_modules/hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true, - "license": "ISC" - }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", @@ -6186,9 +5239,9 @@ } }, "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -6269,21 +5322,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6293,24 +5331,6 @@ "node": ">= 0.10" } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -6318,72 +5338,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", @@ -6400,41 +5354,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -6445,22 +5364,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -6484,8 +5387,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", @@ -6512,201 +5415,37 @@ "node": ">=0.10.0" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.12.0" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "license": "MIT", + "optional": true, "dependencies": { - "call-bound": "^1.0.3" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -6715,29 +5454,24 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT", + "optional": true }, "node_modules/isexe": { "version": "2.0.0", @@ -6803,9 +5537,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6831,49 +5565,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/jake/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -7527,13 +6218,6 @@ "dev": true, "license": "MIT" }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true, - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -7707,9 +6391,9 @@ } }, "node_modules/koa": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.1.tgz", - "integrity": "sha512-umfX9d3iuSxTQP4pnzLOz0HKnPg0FaUUIKcye2lOiz3KPu1Y3M3xlz76dISdFPQs37P9eJz1wUpcTS6KDPn9fA==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.16.2.tgz", + "integrity": "sha512-+CCssgnrWKx9aI3OeZwroa/ckG4JICxvIFnSiOUyl2Uv+UTI+xIw0FfFrWS7cQFpoePpr9o8csss7KzsTzNL8Q==", "license": "MIT", "optional": true, "dependencies": { @@ -7849,9 +6533,9 @@ } }, "node_modules/libphonenumber-js": { - "version": "1.12.9", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.9.tgz", - "integrity": "sha512-VWwAdNeJgN7jFOD+wN4qx83DTPMVPPAUyx9/TUkBXKLiNkuWWk6anV0439tgdtwaJDrEdqkvdN22iA6J4bUCZg==", + "version": "1.12.17", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.17.tgz", + "integrity": "sha512-bsxi8FoceAYR/bjHcLYc2ShJ/aVAzo5jaxAYiMHF0BD+NTp47405CGuPNKYpw+lHadN9k/ClFGc9X5vaZswIrA==", "license": "MIT" }, "node_modules/limiter": { @@ -7866,46 +6550,6 @@ "dev": true, "license": "MIT" }, - "node_modules/load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/load-json-file/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -8131,15 +6775,6 @@ "node": ">= 0.6" } }, - "node_modules/memorystream": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", - "dev": true, - "engines": { - "node": ">= 0.10.0" - } - }, "node_modules/merge-descriptors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", @@ -8234,9 +6869,10 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -8322,10 +6958,10 @@ "node": ">= 0.6" } }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, @@ -8394,35 +7030,12 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.21", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", + "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", "dev": true, "license": "MIT" }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -8430,208 +7043,7 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", - "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "chalk": "^2.4.1", - "cross-spawn": "^6.0.5", - "memorystream": "^0.3.1", - "minimatch": "^3.0.4", - "pidtree": "^0.3.0", - "read-pkg": "^3.0.0", - "shell-quote": "^1.6.1", - "string.prototype.padend": "^3.0.0" - }, - "bin": { - "npm-run-all": "bin/npm-run-all/index.js", - "run-p": "bin/run-p/index.js", - "run-s": "bin/run-s/index.js" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/npm-run-all/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/npm-run-all/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/npm-run-all/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true, - "license": "MIT" - }, - "node_modules/npm-run-all/node_modules/cross-spawn": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.6.tgz", - "integrity": "sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/npm-run-all/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/npm-run-all/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/npm-run-all/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/npm-run-all/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-all/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/npm-run-all/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "node": ">=0.10.0" } }, "node_modules/npm-run-path": { @@ -8678,106 +7090,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.groupby": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", - "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -8791,9 +7103,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "optional": true, "engines": { @@ -8866,24 +7178,6 @@ "node": ">= 0.8.0" } }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -9037,16 +7331,13 @@ "license": "MIT" }, "node_modules/path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, "license": "MIT", - "dependencies": { - "pify": "^3.0.0" - }, "engines": { - "node": ">=4" + "node": ">=8" } }, "node_modules/pause-stream": { @@ -9063,22 +7354,22 @@ } }, "node_modules/pg": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.0.tgz", - "integrity": "sha512-7SKfdvP8CTNXjMUzfcVTaI+TDzBEeaUnVwiVGZQD1Hh33Kpev7liQba9uLd4CfN8r9mCVsD0JIpq03+Unpz+kg==", + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.9.0", - "pg-pool": "^3.10.0", - "pg-protocol": "^1.10.0", + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "engines": { - "node": ">= 8.0.0" + "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.2.5" + "pg-cloudflare": "^1.2.7" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -9090,16 +7381,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.5.tgz", - "integrity": "sha512-OOX22Vt0vOSRrdoUPKJ8Wi2OpE/o/h9T8X1s4qSkCedbNah9ei2W2765be8iMVxQUsvgT7zIAT2eIa9fs5+vtg==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.0.tgz", - "integrity": "sha512-P2DEBKuvh5RClafLngkAuGe9OUlFV7ebu8w1kmaaOgPcpJd1RIFh7otETfI6hAR8YupOLFTY7nuvvIn7PLciUQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", "license": "MIT" }, "node_modules/pg-int8": { @@ -9112,18 +7403,18 @@ } }, "node_modules/pg-pool": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.0.tgz", - "integrity": "sha512-DzZ26On4sQ0KmqnO34muPcmKbhrjmyiO4lCCR0VwEd7MjmiKf5NTg/6+apUEu0NF7ESa37CGzFxH513CoUmWnA==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", "license": "MIT", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.0.tgz", - "integrity": "sha512-IpdytjudNuLv8nhlHs/UrVBhU0e78J0oIS/0AVdTbWxSOkFUVdsHC/NrorO6nXsQNDTT1kzDSOMJubBQviX18Q==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", "license": "MIT" }, "node_modules/pg-types": { @@ -9171,29 +7462,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pidtree": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", - "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==", - "dev": true, - "license": "MIT", - "bin": { - "pidtree": "bin/pidtree.js" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/pirates": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", @@ -9273,16 +7541,6 @@ "node": ">=8" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/postgres-array": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", @@ -9333,9 +7591,9 @@ } }, "node_modules/prettier": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz", - "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", "dev": true, "license": "MIT", "bin": { @@ -9411,9 +7669,9 @@ } }, "node_modules/protobufjs": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.3.tgz", - "integrity": "sha512-sildjKwVqOI2kmFDiXQ6aEB0fjYTafpEvIBs8tOR8qI4spuL9OPROLVu2qZqi/xgCfsHIwVqlaF8JBjWFHnKbw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", + "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", "hasInstallScript": true, "license": "BSD-3-Clause", "optional": true, @@ -9598,21 +7856,6 @@ "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", - "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -9634,50 +7877,6 @@ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -9742,16 +7941,6 @@ "node": ">=4" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, "node_modules/resolve.exports": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", @@ -9918,6 +8107,21 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/routing-controllers/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/routing-controllers/node_modules/reflect-metadata": { "version": "0.1.14", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", @@ -9948,26 +8152,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -9988,29 +8172,12 @@ ], "license": "MIT" }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "devOptional": true, "license": "MIT", + "optional": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -10116,55 +8283,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -10192,19 +8310,6 @@ "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -10313,51 +8418,15 @@ }, "node_modules/source-map-support": { "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdx-correct": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", - "dev": true, - "license": "CC-BY-3.0" - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, "license": "MIT", "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "node_modules/spdx-license-ids": { - "version": "3.0.21", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.21.tgz", - "integrity": "sha512-Bvg/8F5XephndSK3JffaRqdT+gyhfqIPwDHpX80tJrF8QQRYMo8sNMeaZ2Dp5+jhwKnUmIOyFFQfHRkjJm5nXg==", - "dev": true, - "license": "CC0-1.0" - }, "node_modules/split": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", @@ -10419,20 +8488,6 @@ "node": ">= 0.8" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/stream-combiner": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", @@ -10532,84 +8587,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.padend": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.6.tgz", - "integrity": "sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10799,9 +8776,9 @@ } }, "node_modules/swagger-ui-dist": { - "version": "5.24.1", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.24.1.tgz", - "integrity": "sha512-ITeWc7CCAfK53u8jnV39UNqStQZjSt+bVYtJHsOEL3vVj/WV9/8HmsF8Ej4oD8r+Xk1HpWyeW/t59r1QNeAcUQ==", + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.29.0.tgz", + "integrity": "sha512-gqs7Md3AxP4mbpXAq31o5QW+wGUZsUzVatg70yXpUR245dfIis5jAzufBd+UQM/w2xSfrhvA1eqsrgnl2PbezQ==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "=1.4.0" @@ -10822,16 +8799,6 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, - "node_modules/tapable": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.2.tgz", - "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/teeny-request": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", @@ -10985,64 +8952,28 @@ "license": "MIT" }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dev": true, "license": "MIT", "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-declaration-location": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/ts-declaration-location/-/ts-declaration-location-1.0.7.tgz", - "integrity": "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==", - "dev": true, - "funding": [ - { - "type": "ko-fi", - "url": "https://ko-fi.com/rebeccastevens" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/ts-declaration-location" - } - ], - "license": "BSD-3-Clause", - "dependencies": { - "picomatch": "^4.0.2" + "node": ">=16" }, "peerDependencies": { - "typescript": ">=4.0.0" - } - }, - "node_modules/ts-declaration-location/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "typescript": ">=4.2.0" } }, "node_modules/ts-jest": { - "version": "29.4.0", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.0.tgz", - "integrity": "sha512-d423TJMnJGu80/eSgfQ5w/R+0zFJvdtTxwtF9KzFFunOpSeD+79lHJQIiAhluJoyGRbvj9NZJsl9WjCUo0ND7Q==", + "version": "29.4.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.1.tgz", + "integrity": "sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", - "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", @@ -11165,42 +9096,6 @@ "typescript": "*" } }, - "node_modules/tsconfig-paths": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", - "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/tsconfig-paths/node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -11266,84 +9161,6 @@ "node": ">= 0.6" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -11358,9 +9175,9 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, "license": "Apache-2.0", "bin": { @@ -11371,6 +9188,20 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -11384,25 +9215,6 @@ "node": ">= 0.8" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -11510,17 +9322,6 @@ "node": ">=10.12.0" } }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, "node_modules/validator": { "version": "13.15.15", "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.15.tgz", @@ -11603,95 +9404,6 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -11702,6 +9414,13 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -11806,9 +9525,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { diff --git a/package.json b/package.json index 5ae7ab9..cc6dc5a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "type": "private", "url": "https://github.com/bonadocs/api" }, - "prettier": "@bonadocs/prettier-config", + "prettier": {}, "scripts": { "build": "rm -rf dist/ && tsc -p tsconfig.build.json", "watch:build": "tsc -p tsconfig.build.json -w", @@ -27,8 +27,6 @@ "lint": "prettier -c \"src/**/*.ts{,x}\" && eslint src --ext .ts" }, "dependencies": { - "@bonadocs/di": "^1.0.0", - "@bonadocs/logger": "^1.0.0", "@paddle/paddle-node-sdk": "^2.7.0", "axios": "^1.7.9", "body-parser": "^1.20.3", @@ -53,7 +51,6 @@ "typedi": "^0.10.0" }, "devDependencies": { - "@bonadocs/eslint-config": "latest", "@jest/globals": "^29.7.0", "@types/cors": "^2.8.17", "@types/node": "^22.5.4", @@ -62,8 +59,11 @@ "@types/randomstring": "^1.3.0", "@types/swagger-jsdoc": "^6.0.4", "@types/swagger-ui-express": "^4.1.7", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "class-validator-jsonschema": "^5.0.1", "dotenv": "^16.4.5", + "eslint": "^8.57.1", "husky": "^9.1.7", "jest": "^29.7.0", "prettier": "^3.4.2", diff --git a/src/app.ts b/src/app.ts index b14d649..565048d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,13 +1,13 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { validationMetadatasToSchemas } from 'class-validator-jsonschema'; -import express from 'express'; -import helmet from 'helmet'; -import { getMetadataArgsStorage, useExpressServer } from 'routing-controllers'; -import { routingControllersToSpec } from 'routing-controllers-openapi'; -import swaggerUi from 'swagger-ui-express'; +import { validationMetadatasToSchemas } from "class-validator-jsonschema"; +import express from "express"; +import helmet from "helmet"; +import { getMetadataArgsStorage, useExpressServer } from "routing-controllers"; +import { routingControllersToSpec } from "routing-controllers-openapi"; +import swaggerUi from "swagger-ui-express"; -import { diMiddleware, useScopedContainer } from '@bonadocs/di'; -import { rootLoggerMiddleware } from '@bonadocs/logger'; +import { diMiddleware, useScopedContainer } from "@bonadocs/di"; +import { rootLoggerMiddleware } from "@bonadocs/logger"; import { AdminMiddleware, @@ -16,7 +16,7 @@ import { healthcheckMiddleware, useCors, webhookMiddleware, -} from './middleware'; +} from "./middleware"; import { AuthController, BillingController, @@ -26,12 +26,12 @@ import { ProjectController, SubscriptionController, TenderlyController, -} from './modules'; +} from "./modules"; import { MonitorPaymentStatus, SubscriptionExpirationCheck, SubscriptionRenewalSetup, -} from './modules/cron'; +} from "./modules/cron"; const app = express(); const configService = getConfigService(); @@ -39,16 +39,16 @@ const configService = getConfigService(); // add middleware app.use(helmet()); useCors(app); -app.use('/v1/billing/webhook', express.raw({ type: '*/*' })); +app.use("/v1/billing/webhook", express.raw({ type: "*/*" })); app.use(webhookMiddleware); -app.use(express.json({ limit: '300kb' })); +app.use(express.json({ limit: "300kb" })); // add this before the logger to avoid unnecessary healthcheck // request logs app.use(healthcheckMiddleware); const schemas = validationMetadatasToSchemas({ - refPointerPrefix: '#/components/schemas/', + refPointerPrefix: "#/components/schemas/", }); const spec = routingControllersToSpec( @@ -59,13 +59,13 @@ const spec = routingControllersToSpec( schemas: schemas as any, securitySchemes: { bearerAuth: { - type: 'http', - scheme: 'bearer', - bearerFormat: 'JWT', + type: "http", + scheme: "bearer", + bearerFormat: "JWT", }, }, }, - info: { title: 'API Docs', version: '1.0.0' }, + info: { title: "API Docs", version: "1.0.0" }, security: [{ bearerAuth: [] }], }, ); @@ -78,7 +78,7 @@ app.use(diMiddleware); // use the scoped container for routing-controllers useScopedContainer(); -const routePrefix = '/v1'; +const routePrefix = "/v1"; spec.paths = Object.keys(spec.paths).reduce( (paths, path) => { paths[`${routePrefix}${path}`] = spec.paths[path]; @@ -87,7 +87,7 @@ spec.paths = Object.keys(spec.paths).reduce( {} as Record, ); -app.use('/docs', ...swaggerUi.serve, swaggerUi.setup(spec)); +app.use("/docs", ...swaggerUi.serve, swaggerUi.setup(spec)); useExpressServer(app, { routePrefix, diff --git a/src/env.ts b/src/env.ts index a2d98d2..98dfb34 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,4 +1,4 @@ -import { validateEnvVars } from './modules/configuration'; +import { validateEnvVars } from "./modules/configuration"; // This is run on startup to ensure that all required environment variables are // set before the application starts diff --git a/src/localserver.ts b/src/localserver.ts index 4345362..27b175c 100644 --- a/src/localserver.ts +++ b/src/localserver.ts @@ -1,2 +1,2 @@ -import 'dotenv/config'; -import './server'; +import "dotenv/config"; +import "./server"; diff --git a/src/middleware/admin.ts b/src/middleware/admin.ts index fedff4e..672e30a 100644 --- a/src/middleware/admin.ts +++ b/src/middleware/admin.ts @@ -1,36 +1,44 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Request, Response } from 'express'; -import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +import { Request, Response } from "express"; +import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; @Service() -@Middleware({ type: 'before' }) +@Middleware({ type: "before" }) export class AdminMiddleware implements ExpressMiddlewareInterface { - constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} + constructor( + @Inject(diConstants.logger) private readonly logger: BonadocsLogger, + ) {} - async use(request: Request, response: Response, next: (err?: any) => any): Promise { + async use( + request: Request, + response: Response, + next: (err?: any) => any, + ): Promise { // todo : find a better fix for this - const adminPaths = ['/coupons']; - const isAdminPath = adminPaths.some((path) => request.path.startsWith(path)); + const adminPaths = ["/coupons"]; + const isAdminPath = adminPaths.some((path) => + request.path.startsWith(path), + ); if (!isAdminPath) { return next(); } - const token = request.headers.authorization?.split(' ')[1]; + const token = request.headers.authorization?.split(" ")[1]; if (!token) { - this.logger.error('Admin token is missing'); + this.logger.error("Admin token is missing"); return response.status(401).json({ - message: 'Admin token is missing', - status: 'error', + message: "Admin token is missing", + status: "error", }); } if (token !== process.env.ADMIN_TOKEN) { - this.logger.error('Invalid admin token'); + this.logger.error("Invalid admin token"); return response.status(403).json({ - message: 'Invalid admin token', - status: 'error', + message: "Invalid admin token", + status: "error", }); } return next(); diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts index be1cb47..f387f63 100644 --- a/src/middleware/auth.ts +++ b/src/middleware/auth.ts @@ -1,47 +1,56 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Request, Response } from 'express'; -import { ExpressMiddlewareInterface, Middleware } from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +import { Request, Response } from "express"; +import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import type { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import type { BonadocsLogger } from "@bonadocs/logger"; -import { AuthService } from '../modules/auth/auth.service'; -import { ApplicationError, applicationErrorCodes } from '../modules/errors/ApplicationError'; +import { AuthService } from "../modules/auth/auth.service"; +import { + ApplicationError, + applicationErrorCodes, +} from "../modules/errors/ApplicationError"; @Service() -@Middleware({ type: 'before' }) +@Middleware({ type: "before" }) export class AuthMiddleware implements ExpressMiddlewareInterface { constructor( @Inject(diConstants.logger) private readonly logger: BonadocsLogger, @Inject() private readonly authService: AuthService, ) {} - async use(request: Request, response: Response, next: (err?: any) => any): Promise { + async use( + request: Request, + response: Response, + next: (err?: any) => any, + ): Promise { // todo : find a better fix for this const exemptPaths = [ - '/auth/login', - '/auth/register', - '/auth/refresh', - '/billing/webhook', - '/coupons', + "/auth/login", + "/auth/register", + "/auth/refresh", + "/billing/webhook", + "/coupons", ]; - const isExemptPath = exemptPaths.some((path) => request.path.startsWith(path)); + const isExemptPath = exemptPaths.some((path) => + request.path.startsWith(path), + ); if (isExemptPath) { return next(); } - const token = request.headers.authorization?.split(' ')[1]; + const token = request.headers.authorization?.split(" ")[1]; try { const authData = await this.authService.getCurrentUser(token!); request.auth = authData; } catch (error) { this.logger.error(`An error occurred while validating token, ${error}`); throw new ApplicationError({ - message: 'Invalid token', + message: "Invalid token", logger: request.logger, errorCode: applicationErrorCodes.unauthenticated, - userFriendlyMessage: 'Invalid Token', + userFriendlyMessage: "Invalid Token", statusCode: 401, }); } diff --git a/src/middleware/cors.ts b/src/middleware/cors.ts index cbeefab..819e69b 100644 --- a/src/middleware/cors.ts +++ b/src/middleware/cors.ts @@ -1,16 +1,16 @@ -import cors from 'cors'; -import { Application } from 'express'; +import cors from "cors"; +import { Application } from "express"; -import { getConfigService } from '../modules'; +import { getConfigService } from "../modules"; export function useCors(app: Application): void { const configService = getConfigService(); app.use( cors({ - origin: configService.getTransformed('corsOrigins'), + origin: configService.getTransformed("corsOrigins"), maxAge: 3600, - methods: '*', - allowedHeaders: 'Content-Type, Authorization', + methods: "*", + allowedHeaders: "Content-Type, Authorization", }), ); } diff --git a/src/middleware/errors.ts b/src/middleware/errors.ts index fdbdbcb..6e2b1af 100644 --- a/src/middleware/errors.ts +++ b/src/middleware/errors.ts @@ -1,17 +1,28 @@ -import { ValidationError } from 'class-validator'; -import { NextFunction, Request, Response } from 'express'; -import { StatusCodes } from 'http-status-codes'; -import { ExpressErrorMiddlewareInterface, HttpError, Middleware } from 'routing-controllers'; -import { Service } from 'typedi'; +import { ValidationError } from "class-validator"; +import { NextFunction, Request, Response } from "express"; +import { StatusCodes } from "http-status-codes"; +import { + ExpressErrorMiddlewareInterface, + HttpError, + Middleware, +} from "routing-controllers"; +import { Service } from "typedi"; -import { getGlobalLogger } from '@bonadocs/di'; +import { getGlobalLogger } from "@bonadocs/di"; -import { ApplicationError } from '../modules/errors'; +import { ApplicationError } from "../modules/errors"; @Service() -@Middleware({ type: 'after' }) -export default class AppErrorHandler implements ExpressErrorMiddlewareInterface { - error(error: unknown, request: Request, response: Response, _: NextFunction): void { +@Middleware({ type: "after" }) +export default class AppErrorHandler + implements ExpressErrorMiddlewareInterface +{ + error( + error: unknown, + request: Request, + response: Response, + _next: NextFunction, + ): void { if (error instanceof HttpError) { handleHttpError(request, response, error); return; @@ -25,48 +36,52 @@ export default class AppErrorHandler implements ExpressErrorMiddlewareInterface return; } - if ((error).name === 'PayloadTooLargeError') { + if ((error).name === "PayloadTooLargeError") { response.status(StatusCodes.REQUEST_TOO_LONG).json({ - status: 'failed', - message: 'Request payload too large', + status: "failed", + message: "Request payload too large", }); return; } - if ((error).name === 'PayloadTooLargeError') { + if ((error).name === "PayloadTooLargeError") { response.status(StatusCodes.REQUEST_TOO_LONG).json({ - status: 'failed', - message: 'Request payload too large', + status: "failed", + message: "Request payload too large", }); return; } if (request.logger) { - request.logger?.error('Unexpected error occurred', error); + request.logger?.error("Unexpected error occurred", error); } else { - getGlobalLogger().error('Unexpected error occurred', error); + getGlobalLogger().error("Unexpected error occurred", error); } response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ - status: 'failed', - message: 'An error occurred while processing your request', + status: "failed", + message: "An error occurred while processing your request", }); } } -function handleHttpError(request: Request, response: Response, error: HttpError) { - request.logger?.error('HTTP Error occurred', error); +function handleHttpError( + request: Request, + response: Response, + error: HttpError, +) { + request.logger?.error("HTTP Error occurred", error); if (hasValidationErrors(error)) { const errors = handleValidationErrors(error.errors); return response.status(error.httpCode).json({ - status: 'failed', - message: 'Invalid payload', + status: "failed", + message: "Invalid payload", errors, }); } return response.status(error.httpCode).json({ - status: 'failed', + status: "failed", message: error.message, }); } @@ -75,18 +90,23 @@ type HasValidationErrors = { errors: ValidationError[] }; function hasValidationErrors(error: object): error is HasValidationErrors { return ( - 'errors' in error && Array.isArray(error.errors) && error.errors[0] instanceof ValidationError + "errors" in error && + Array.isArray(error.errors) && + error.errors[0] instanceof ValidationError ); } -function handleValidationErrors(errors: ValidationError[], parentPath = ''): unknown[] { +function handleValidationErrors( + errors: ValidationError[], + parentPath = "", +): unknown[] { const errorMessages: unknown[] = []; for (const error of errors) { if (error.constraints) { errorMessages.push({ field: `${parentPath}${error.property}`, - message: Object.values(error.constraints).join(' | '), + message: Object.values(error.constraints).join(" | "), }); } diff --git a/src/middleware/healthcheck.ts b/src/middleware/healthcheck.ts index 11be029..437fe34 100644 --- a/src/middleware/healthcheck.ts +++ b/src/middleware/healthcheck.ts @@ -1,12 +1,12 @@ -import type { NextFunction, Request, Response } from 'express'; +import type { NextFunction, Request, Response } from "express"; export default function healthcheckMiddleware( request: Request, response: Response, next: NextFunction, ): void { - if (request.url === '/healthcheck') { - response.send('

Healthcheck OK! 👍

'); + if (request.url === "/healthcheck") { + response.send("

Healthcheck OK! 👍

"); return; } diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 4100eb9..8f8cef1 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1,6 +1,6 @@ -export { default as AppErrorHandler } from './errors'; -export { default as healthcheckMiddleware } from './healthcheck'; -export { useCors } from './cors'; -export { AuthMiddleware } from './auth'; -export { AdminMiddleware } from './admin'; -export { default as webhookMiddleware } from './webhook'; +export { default as AppErrorHandler } from "./errors"; +export { default as healthcheckMiddleware } from "./healthcheck"; +export { useCors } from "./cors"; +export { AuthMiddleware } from "./auth"; +export { AdminMiddleware } from "./admin"; +export { default as webhookMiddleware } from "./webhook"; diff --git a/src/middleware/webhook.ts b/src/middleware/webhook.ts index eb1b220..ae2be33 100644 --- a/src/middleware/webhook.ts +++ b/src/middleware/webhook.ts @@ -1,10 +1,10 @@ -import { NextFunction, Request, Response } from 'express'; -import { StatusCodes } from 'http-status-codes'; -import Container from 'typedi'; +import { NextFunction, Request, Response } from "express"; +import { StatusCodes } from "http-status-codes"; +import Container from "typedi"; -import { getGlobalLogger } from '@bonadocs/di'; +import { getGlobalLogger } from "@bonadocs/di"; -import { BillingService } from '../modules/billing/billing.service'; +import { BillingService } from "../modules/billing/billing.service"; export default function webhookMiddleware( request: Request, @@ -13,14 +13,14 @@ export default function webhookMiddleware( ): void | Promise { const billingService = Container.get(BillingService); const logger = getGlobalLogger(); - if (request.path.startsWith('/v1/billing/webhook')) { + if (request.path.startsWith("/v1/billing/webhook")) { billingService .verifyWebhookSignature(request) .then(async (eventData) => { if (eventData === null) { return response.status(StatusCodes.BAD_REQUEST).json({ - status: 'failed', - message: 'Unable to verify event signature', + status: "failed", + message: "Unable to verify event signature", }); } @@ -28,21 +28,21 @@ export default function webhookMiddleware( if (result) { return response.status(StatusCodes.OK).json({ - status: 'successful', - message: 'Event handled successfully', + status: "successful", + message: "Event handled successfully", }); } return response.status(StatusCodes.BAD_REQUEST).json({ - status: 'failed', - message: 'Unable to handle event', + status: "failed", + message: "Unable to handle event", }); }) .catch((error) => { - logger.error('Webhook middleware error:', error); + logger.error("Webhook middleware error:", error); return response.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ - status: 'failed', - message: 'Internal server error', + status: "failed", + message: "Internal server error", }); }); diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 75f2a16..e2be875 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -1,8 +1,8 @@ -import { Request } from 'express'; -import { Body, JsonController, Post, Req } from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +import { Request } from "express"; +import { Body, JsonController, Post, Req } from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { JsonResponse } from '../shared'; +import { JsonResponse } from "../shared"; import type { LoginUserRequest, @@ -12,18 +12,18 @@ import type { RegisterUserRequest, RegisterUserResponse, WSTokenResponse, -} from './auth.interface'; -import { AuthService } from './auth.service'; -import { LoginDto } from './dto/login.dto'; -import { RegisterUserDto } from './dto/register.dto'; -import { WSLoginDto } from './dto/wslogin.dto'; +} from "./auth.interface"; +import { AuthService } from "./auth.service"; +import { LoginDto } from "./dto/login.dto"; +import { RegisterUserDto } from "./dto/register.dto"; +import { WSLoginDto } from "./dto/wslogin.dto"; @Service() -@JsonController('/auth') +@JsonController("/auth") export class AuthController { constructor(@Inject() private readonly authService: AuthService) {} - @Post('/login') + @Post("/login") async login( @Body({ validate: true }) payload: LoginDto, ): Promise> { @@ -31,12 +31,12 @@ export class AuthController { const response = await this.authService.login(request); return { data: response, - status: 'successful', - message: 'Login successful', + status: "successful", + message: "Login successful", }; } - @Post('/register') + @Post("/register") async register( @Body({ validate: true }) payload: RegisterUserDto, ): Promise> { @@ -44,24 +44,24 @@ export class AuthController { const response = await this.authService.register(request); return { data: response, - status: 'successful', - message: 'Register successful', + status: "successful", + message: "Register successful", }; } - @Post('/refresh') + @Post("/refresh") async refresh( @Body({ validate: true }) payload: RefreshTokenRequest, ): Promise> { const response = await this.authService.refresh(payload); return { data: response, - status: 'successful', - message: 'Refresh successful', + status: "successful", + message: "Refresh successful", }; } - @Post('/ws') + @Post("/ws") async ws( @Req() request: Request, @Body({ validate: true }) payload: WSLoginDto, @@ -70,8 +70,8 @@ export class AuthController { const response = await this.authService.wsLogin(payload, authData); return { data: response, - status: 'successful', - message: 'Refresh successful', + status: "successful", + message: "Refresh successful", }; } } diff --git a/src/modules/auth/auth.interface.ts b/src/modules/auth/auth.interface.ts index 2187ca6..44b716e 100644 --- a/src/modules/auth/auth.interface.ts +++ b/src/modules/auth/auth.interface.ts @@ -1,4 +1,4 @@ -import { AuthSource } from './auth.types'; +import { AuthSource } from "./auth.types"; export interface LoginUserRequest { authSource: AuthSource; diff --git a/src/modules/auth/auth.service.test.ts b/src/modules/auth/auth.service.test.ts index 54a5aec..82c2939 100644 --- a/src/modules/auth/auth.service.test.ts +++ b/src/modules/auth/auth.service.test.ts @@ -1,19 +1,19 @@ -import 'reflect-metadata'; -import { beforeEach, describe, expect, it, jest } from '@jest/globals'; +import "reflect-metadata"; +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { generateMockObject, getMockLogger } from '../../test/util'; -import { GCP } from '../configuration'; -import { ConfigService } from '../configuration/config.service'; -import { ProjectRepository } from '../repositories/projects/project.repository'; -import { UserRepository } from '../repositories/users/user.repository'; -import { Storage } from '../storage'; +import { generateMockObject, getMockLogger } from "../../test/util"; +import { GCP } from "../configuration"; +import { ConfigService } from "../configuration/config.service"; +import { ProjectRepository } from "../repositories/projects/project.repository"; +import { UserRepository } from "../repositories/users/user.repository"; +import { Storage } from "../storage"; -import { AuthService } from './auth.service'; -import { FirebaseJWTProvider } from './firebase'; +import { AuthService } from "./auth.service"; +import { FirebaseJWTProvider } from "./firebase"; -describe('AuthService', () => { +describe("AuthService", () => { let authService: AuthService; let userRepository: jest.Mocked; let logger: jest.Mocked; @@ -23,17 +23,17 @@ describe('AuthService', () => { let storage: jest.Mocked; beforeEach(() => { - process.env.JWT_SECRET = '47839384hdbsjw9isn393ndu389rndjebdi393hends'; - userRepository = generateMockObject('findUserByAuth'); + process.env.JWT_SECRET = "47839384hdbsjw9isn393ndu389rndjebdi393hends"; + userRepository = generateMockObject("findUserByAuth"); logger = getMockLogger(); - configService = generateMockObject('getTransformed', 'get'); + configService = generateMockObject("getTransformed", "get"); projectRepository = generateMockObject( - 'getProjectCollectionById', - 'checkIfProjectExist', - 'checkUserPermission', + "getProjectCollectionById", + "checkIfProjectExist", + "checkUserPermission", ); - firebaseProvider = generateMockObject('validateJWT'); - storage = generateMockObject('downloadFile'); + firebaseProvider = generateMockObject("validateJWT"); + storage = generateMockObject("downloadFile"); authService = new AuthService( logger, @@ -45,21 +45,21 @@ describe('AuthService', () => { ); }); - describe('login', () => { - it('should login a user', async () => { + describe("login", () => { + it("should login a user", async () => { // arrange userRepository.findUserByAuth.mockResolvedValue({ id: 1, - username: 'test-user', - emailAddress: 'test-email@test.com', - firstName: 'test-first', - lastName: 'test-last', + username: "test-user", + emailAddress: "test-email@test.com", + firstName: "test-first", + lastName: "test-last", }); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', + projectId: "bonadocs", }, } as T; } @@ -69,9 +69,9 @@ describe('AuthService', () => { // act const result = await authService.login({ - authSource: 'firebase', + authSource: "firebase", encodedAuthData: - 'ewogICJ1c2VySWQiOiAiMSIsCiAgImlkVG9rZW4iIDogInVpZGpiMzk5M2RkOTM5ZG5kajM5M254bmVpZW4iCn0=', + "ewogICJ1c2VySWQiOiAiMSIsCiAgImlkVG9rZW4iIDogInVpZGpiMzk5M2RkOTM5ZG5kajM5M254bmVpZW4iCn0=", }); // assert @@ -81,7 +81,7 @@ describe('AuthService', () => { expect(firebaseProvider.validateJWT).toHaveBeenCalled(); }); - it('should throw an error if user is not found', async () => { + it("should throw an error if user is not found", async () => { // arrange userRepository.findUserByAuth.mockResolvedValue(null); @@ -90,29 +90,30 @@ describe('AuthService', () => { // act try { await authService.login({ - authSource: 'firebase', + authSource: "firebase", encodedAuthData: - 'ewogICJ1c2VySWQiOiAiMSIsCiAgImlkVG9rZW4iIDogInVpZGpiMzk5M2RkOTM5ZG5kajM5M254bmVpZW4iCn0=', + "ewogICJ1c2VySWQiOiAiMSIsCiAgImlkVG9rZW4iIDogInVpZGpiMzk5M2RkOTM5ZG5kajM5M254bmVpZW4iCn0=", }); } catch (error) { // assert; expect(error).toEqual({ - errorCode: 'INCOMPLETE_USER_REGISTRATION', + errorCode: "INCOMPLETE_USER_REGISTRATION", statusCode: 400, - userFriendlyMessage: 'Registration is incomplete. Please register and try again', + userFriendlyMessage: + "Registration is incomplete. Please register and try again", }); } }); - describe('wsLogin', () => { - it('should generate a websocket token when given valid API token', async () => { + describe("wsLogin", () => { + it("should generate a websocket token when given valid API token", async () => { // arrange const authData = { isAuthenticated: true, userId: 123, projectId: 1 }; projectRepository.getProjectCollectionById.mockResolvedValue({ id: 1, - name: 'Test Collection', + name: "Test Collection", projectId: 1, - uri: 'test-collection', + uri: "test-collection", isPublic: false, dateLastUpdated: new Date(), }); @@ -121,16 +122,16 @@ describe('AuthService', () => { configService.getTransformed.mockImplementation( (): T => ({ - projectId: '123', - abisBucket: '768', - automergeBucket: '5665', - collectionsBucket: '878', - metaBucket: '1234', + projectId: "123", + abisBucket: "768", + automergeBucket: "5665", + collectionsBucket: "878", + metaBucket: "1234", }) satisfies GCP as T, ); storage.downloadFile.mockResolvedValue( JSON.stringify({ - id: '123', + id: "123", }), ); @@ -149,14 +150,14 @@ describe('AuthService', () => { const decoded = authService.validateJWT(result.token); expect(decoded).toMatchObject({ - purpose: 'ws-api', + purpose: "ws-api", sub: 123, projectId: 1, collectionId: 1, }); }); - it('should throw error when API token is invalid', async () => { + it("should throw error when API token is invalid", async () => { // act & assert const authData = { isAuthenticated: false, userId: 123, projectId: 1 }; await expect( @@ -167,7 +168,7 @@ describe('AuthService', () => { }, authData, ), - ).rejects.toHaveProperty('errorCode', 'UNAUTHENTICATED'); + ).rejects.toHaveProperty("errorCode", "UNAUTHENTICATED"); }); }); }); diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 85d9908..f3e0c35 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,16 +1,16 @@ -import * as crypto from 'crypto'; -import { createHash } from 'crypto'; +import * as crypto from "crypto"; +import { createHash } from "crypto"; -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import type { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import type { BonadocsLogger } from "@bonadocs/logger"; -import { ConfigService, GCP } from '../configuration'; -import { ApplicationError, applicationErrorCodes } from '../errors'; -import { UserRepository } from '../repositories'; -import { ProjectRepository } from '../repositories/projects'; -import { Storage } from '../storage'; +import { ConfigService, GCP } from "../configuration"; +import { ApplicationError, applicationErrorCodes } from "../errors"; +import { UserRepository } from "../repositories"; +import { ProjectRepository } from "../repositories/projects"; +import { Storage } from "../storage"; import { LoginUserRequest, @@ -21,12 +21,15 @@ import { RegisterUserResponse, WSTokenRequest, WSTokenResponse, -} from './auth.interface'; -import { AuthSource } from './auth.types'; -import { FirebaseJWTProvider } from './firebase'; +} from "./auth.interface"; +import { AuthSource } from "./auth.types"; +import { FirebaseJWTProvider } from "./firebase"; // get auth source -type AuthHandler = (logger: BonadocsLogger, authData: Record) => Promise; +type AuthHandler = ( + logger: BonadocsLogger, + authData: Record, +) => Promise; @Service() export class AuthService { @@ -42,30 +45,39 @@ export class AuthService { @Inject() private readonly firebaseJWTProvider: FirebaseJWTProvider, @Inject() private readonly storage: Storage, ) { - this.validityFromEnv = this.configService.get('jwtTokenTtl'); - this.defaultValidity = this.validityFromEnv ? Number(this.validityFromEnv) : 24 * 3600; + this.validityFromEnv = this.configService.get("jwtTokenTtl"); + this.defaultValidity = this.validityFromEnv + ? Number(this.validityFromEnv) + : 24 * 3600; } async login(request: LoginUserRequest): Promise { this.validateEncodedAuthData(request); - const authData = JSON.parse(Buffer.from(request.encodedAuthData, 'base64').toString()); + const authData = JSON.parse( + Buffer.from(request.encodedAuthData, "base64").toString(), + ); const func = this.getHandler(this.logger, request.authSource); const authUserId = await func(this.logger, authData); if (!authUserId) { throw new ApplicationError({ logger: this.logger, - message: 'Failed to authenticate user. Please check your credentials and try again', + message: + "Failed to authenticate user. Please check your credentials and try again", errorCode: applicationErrorCodes.incompleteUserRegistration, - userFriendlyMessage: 'Please check your credentials and try again', + userFriendlyMessage: "Please check your credentials and try again", }); } - const user = await this.userRepository.findUserByAuth(request.authSource, authUserId); + const user = await this.userRepository.findUserByAuth( + request.authSource, + authUserId, + ); if (!user) { throw new ApplicationError({ logger: this.logger, - message: 'Incomplete user profile registration', + message: "Incomplete user profile registration", errorCode: applicationErrorCodes.incompleteUserRegistration, - userFriendlyMessage: 'Registration is incomplete. Please register and try again', + userFriendlyMessage: + "Registration is incomplete. Please register and try again", }); } @@ -83,28 +95,33 @@ export class AuthService { async register(request: RegisterUserRequest): Promise { this.validateEncodedAuthData(request); let authUserId: string | null = null; - const authData = JSON.parse(Buffer.from(request.encodedAuthData, 'base64').toString()); - if (request.authSource === 'firebase') { + const authData = JSON.parse( + Buffer.from(request.encodedAuthData, "base64").toString(), + ); + if (request.authSource === "firebase") { authUserId = await this.firebaseAuthHandler(authData); } if (!authUserId) { throw new ApplicationError({ errorCode: applicationErrorCodes.unauthenticated, - message: 'Failed to authenticate user', + message: "Failed to authenticate user", logger: this.logger, - userFriendlyMessage: 'Please check your credentials and try again', + userFriendlyMessage: "Please check your credentials and try again", statusCode: 401, }); } // check if user already exists - const user = await this.userRepository.findUserByAuth(request.authSource, authUserId); + const user = await this.userRepository.findUserByAuth( + request.authSource, + authUserId, + ); if (user) { throw new ApplicationError({ errorCode: applicationErrorCodes.invalidRequest, - message: 'User already exists', + message: "User already exists", logger: this.logger, - userFriendlyMessage: 'User already exists', + userFriendlyMessage: "User already exists", }); } @@ -122,9 +139,9 @@ export class AuthService { if (!createdUserId) { throw new ApplicationError({ errorCode: applicationErrorCodes.invalidRequest, - message: 'Username or email already exists', + message: "Username or email already exists", logger: this.logger, - userFriendlyMessage: 'Username or email already exists', + userFriendlyMessage: "Username or email already exists", }); } @@ -145,7 +162,7 @@ export class AuthService { const token = this.generateJWT({ userId: authData.userId, - authSource: 'refresh-token', + authSource: "refresh-token", projectId: authData.projectId, }); return { @@ -153,21 +170,26 @@ export class AuthService { }; } - async wsLogin(request: WSTokenRequest, authData: AuthData): Promise { + async wsLogin( + request: WSTokenRequest, + authData: AuthData, + ): Promise { if (!authData.isAuthenticated) { throw new ApplicationError({ - message: 'User is not authenticated', + message: "User is not authenticated", logger: this.logger, errorCode: applicationErrorCodes.unauthenticated, statusCode: 401, - userFriendlyMessage: 'You must be logged in to access this service', + userFriendlyMessage: "You must be logged in to access this service", }); } // check if projectId and collectionId are provided exits - const projectExist = await this.projectRepository.checkIfProjectExist(request.projectId); + const projectExist = await this.projectRepository.checkIfProjectExist( + request.projectId, + ); if (!projectExist) { throw new ApplicationError({ - message: 'Project not found', + message: "Project not found", logger: this.logger, errorCode: applicationErrorCodes.unauthorized, statusCode: 403, @@ -179,7 +201,7 @@ export class AuthService { ); if (!collection) { throw new ApplicationError({ - message: 'Collection not found', + message: "Collection not found", logger: this.logger, errorCode: applicationErrorCodes.unauthorized, statusCode: 403, @@ -187,10 +209,14 @@ export class AuthService { } // check if user has permission to write to the collection - const hasPermission = await this.checkIfUserHasPermission(request.projectId, authData.userId!); + const hasPermission = await this.checkIfUserHasPermission( + request.projectId, + authData.userId!, + ); if (!hasPermission) { throw new ApplicationError({ - message: 'You do not have permission to write in this collection, contact your admin', + message: + "You do not have permission to write in this collection, contact your admin", logger: this.logger, errorCode: applicationErrorCodes.unauthorized, statusCode: 403, @@ -198,13 +224,13 @@ export class AuthService { } const content = await this.storage.downloadFile( - this.configService.getTransformed('gcp').collectionsBucket, + this.configService.getTransformed("gcp").collectionsBucket, collection.uri, ); if (!content) { throw new ApplicationError({ - message: 'Collection file not found', + message: "Collection file not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); @@ -213,16 +239,18 @@ export class AuthService { const docId = JSON.parse(content).id; if (!docId) { throw new ApplicationError({ - message: 'Collection document ID not found', + message: "Collection document ID not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } - const validityPeriod = this.validityFromEnv ? Number(this.validityFromEnv) : 6 * 3600; + const validityPeriod = this.validityFromEnv + ? Number(this.validityFromEnv) + : 6 * 3600; const token = this.generateJWT( { - purpose: 'ws-api', + purpose: "ws-api", sub: authData.userId, projectId: request.projectId, collectionId: request.collectionId, @@ -237,15 +265,19 @@ export class AuthService { }; } - async checkIfUserHasPermission(projectId: number, userId: number): Promise { + async checkIfUserHasPermission( + projectId: number, + userId: number, + ): Promise { const hasWritePermission = await this.projectRepository.checkUserPermission( projectId, userId, - 'writeCollections', + "writeCollections", ); if (!hasWritePermission) { throw new ApplicationError({ - message: 'You do not have permission to write in this collection, contact your admin', + message: + "You do not have permission to write in this collection, contact your admin", errorCode: applicationErrorCodes.unauthorized, statusCode: 403, logger: this.logger, @@ -256,9 +288,9 @@ export class AuthService { getHandler(logger: BonadocsLogger, authSource: AuthSource): AuthHandler { const func = this.authSourceHandlers[authSource]; - if (!func || typeof func !== 'function') { + if (!func || typeof func !== "function") { throw new ApplicationError({ - message: 'Unsupported auth source', + message: "Unsupported auth source", logger: this.logger, errorCode: applicationErrorCodes.unauthenticated, statusCode: 401, @@ -267,32 +299,37 @@ export class AuthService { return func; } - generateJWT(payload: Record, validity = this.defaultValidity): string { + generateJWT( + payload: Record, + validity = this.defaultValidity, + ): string { const header = JSON.stringify({ - alg: 'HS256', - typ: 'JWT', + alg: "HS256", + typ: "JWT", }); - payload.iss = 'bonadocs'; + payload.iss = "bonadocs"; const nowSeconds = Math.floor(Date.now() / 1000); payload.exp = nowSeconds + validity; payload.iat = nowSeconds; payload.nbf = nowSeconds; - const encodedHeader = Buffer.from(header).toString('base64url'); - const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64url'); + const encodedHeader = Buffer.from(header).toString("base64url"); + const encodedPayload = Buffer.from(JSON.stringify(payload)).toString( + "base64url", + ); const signature = crypto - .createHmac('sha256', process.env.JWT_SECRET!) + .createHmac("sha256", process.env.JWT_SECRET!) .update(`${encodedHeader}.${encodedPayload}`) - .digest('base64url'); + .digest("base64url"); return `${encodedHeader}.${encodedPayload}.${signature}`; } validateJWT(token: string): Record | false { - const parts = token.split('.'); + const parts = token.split("."); if (parts.length !== 3) { return false; } @@ -300,11 +337,15 @@ export class AuthService { const [encodedHeader, encodedPayload, signature] = parts; try { - const header = JSON.parse(Buffer.from(encodedHeader, 'base64url').toString()); + const header = JSON.parse( + Buffer.from(encodedHeader, "base64url").toString(), + ); - const payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString()); + const payload = JSON.parse( + Buffer.from(encodedPayload, "base64url").toString(), + ); - if (header.alg !== 'HS256' || header.typ !== 'JWT') { + if (header.alg !== "HS256" || header.typ !== "JWT") { return false; } @@ -321,36 +362,39 @@ export class AuthService { } const newSignature = crypto - .createHmac('sha256', process.env.JWT_SECRET!) + .createHmac("sha256", process.env.JWT_SECRET!) .update(`${encodedHeader}.${encodedPayload}`) - .digest('base64url'); + .digest("base64url"); if (newSignature !== signature) { return false; } return payload; } catch { - this.logger.error('Error occurred while validating provided token'); + this.logger.error("Error occurred while validating provided token"); return false; } } async getCurrentUser(token: string): Promise { // token = token.slice(7); - if (token.startsWith('bta_')) { + if (token.startsWith("bta_")) { return this.authenticateProjectApiToken(token); } return this.authenticateUserToken(token); } private async authenticateProjectApiToken(token: string): Promise { - this.logger.info('Authenticating project API token'); - const apiKeyHash = createHash('sha256').update(token).digest('hex'); - const project = await this.projectRepository.getProjectByAPIKeyHash(apiKeyHash, null); + this.logger.info("Authenticating project API token"); + const apiKeyHash = createHash("sha256").update(token).digest("hex"); + const project = await this.projectRepository.getProjectByAPIKeyHash( + apiKeyHash, + null, + ); if (!project) { throw new ApplicationError({ - message: 'Invalid or expired API key', + message: "Invalid or expired API key", logger: this.logger, errorCode: applicationErrorCodes.unauthenticated, statusCode: 401, @@ -364,21 +408,23 @@ export class AuthService { } private authenticateUserToken(token: string): AuthData { - this.logger.info('Authenticating user token'); + this.logger.info("Authenticating user token"); const isValid = this.validateJWT(token); if (!isValid) { throw new ApplicationError({ - message: 'Invalid JWT', + message: "Invalid JWT", logger: this.logger, errorCode: applicationErrorCodes.unauthenticated, statusCode: 401, }); } - const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64url').toString()); - if (payload.purpose === 'ws-api') { + const payload = JSON.parse( + Buffer.from(token.split(".")[1], "base64url").toString(), + ); + if (payload.purpose === "ws-api") { throw new ApplicationError({ - message: 'Invalid JWT - purpose ws server', + message: "Invalid JWT - purpose ws server", logger: this.logger, errorCode: applicationErrorCodes.unauthenticated, statusCode: 401, @@ -386,7 +432,7 @@ export class AuthService { } if (!payload.userId) { throw new ApplicationError({ - message: 'Invalid JWT - missing user ID', + message: "Invalid JWT - missing user ID", logger: this.logger, errorCode: applicationErrorCodes.unauthenticated, statusCode: 401, @@ -401,23 +447,29 @@ export class AuthService { } private authSourceHandlers: Record = { - firebase: async (logger: BonadocsLogger, authData: Record) => { + firebase: async ( + logger: BonadocsLogger, + authData: Record, + ) => { const { userId, idToken } = authData; - if (typeof userId !== 'string' || typeof idToken !== 'string') { + if (typeof userId !== "string" || typeof idToken !== "string") { throw new ApplicationError({ - message: 'Invalid auth data', + message: "Invalid auth data", logger: this.logger, errorCode: applicationErrorCodes.unauthenticated, statusCode: 401, }); } - const isValid = await this.firebaseJWTProvider.validateJWT(userId, idToken); + const isValid = await this.firebaseJWTProvider.validateJWT( + userId, + idToken, + ); if (!isValid) { throw new ApplicationError({ - message: 'Invalid Firebase ID Token', + message: "Invalid Firebase ID Token", errorCode: applicationErrorCodes.unauthenticated, logger: this.logger, statusCode: 401, @@ -428,25 +480,29 @@ export class AuthService { }, }; - private validateEncodedAuthData(request: Pick): void { + private validateEncodedAuthData( + request: Pick, + ): void { try { - JSON.parse(Buffer.from(request.encodedAuthData, 'base64').toString()); + JSON.parse(Buffer.from(request.encodedAuthData, "base64").toString()); } catch { throw new ApplicationError({ errorCode: applicationErrorCodes.invalidRequest, - message: 'encodedAuthData is not valid Base64-encoded JSON', + message: "encodedAuthData is not valid Base64-encoded JSON", logger: this.logger, - userFriendlyMessage: 'encodedAuthData is not valid Base64-encoded JSON', + userFriendlyMessage: "encodedAuthData is not valid Base64-encoded JSON", }); } } - private async firebaseAuthHandler(authData: Record): Promise { + private async firebaseAuthHandler( + authData: Record, + ): Promise { const { userId, idToken } = authData; - if (typeof userId !== 'string' || typeof idToken !== 'string') { + if (typeof userId !== "string" || typeof idToken !== "string") { throw new ApplicationError({ - message: 'Invalid auth data', + message: "Invalid auth data", logger: this.logger, errorCode: applicationErrorCodes.unauthenticated, statusCode: 401, @@ -457,7 +513,7 @@ export class AuthService { if (!isValid) { throw new ApplicationError({ - message: 'Invalid Firebase ID Token', + message: "Invalid Firebase ID Token", errorCode: applicationErrorCodes.unauthenticated, logger: this.logger, statusCode: 401, diff --git a/src/modules/auth/auth.types.ts b/src/modules/auth/auth.types.ts index fac98d7..22f53eb 100644 --- a/src/modules/auth/auth.types.ts +++ b/src/modules/auth/auth.types.ts @@ -1,3 +1,3 @@ -export const authSources = ['firebase'] as const; +export const authSources = ["firebase"] as const; export type AuthSource = (typeof authSources)[number]; diff --git a/src/modules/auth/dto/login.dto.ts b/src/modules/auth/dto/login.dto.ts index 9e69648..7d311fd 100644 --- a/src/modules/auth/dto/login.dto.ts +++ b/src/modules/auth/dto/login.dto.ts @@ -1,16 +1,16 @@ -import { IsIn, IsInt, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { IsIn, IsInt, IsNotEmpty, IsOptional, IsString } from "class-validator"; -import * as authTypes from '../auth.types'; +import * as authTypes from "../auth.types"; export class LoginDto { @IsString() - @IsNotEmpty({ message: 'Auth source not provided' }) + @IsNotEmpty({ message: "Auth source not provided" }) @IsIn(authTypes.authSources, { - message: `authSource must be one of: ${authTypes.authSources.join(', ')}`, + message: `authSource must be one of: ${authTypes.authSources.join(", ")}`, }) authSource: authTypes.AuthSource; - @IsNotEmpty({ message: 'Auth data not provided' }) + @IsNotEmpty({ message: "Auth data not provided" }) @IsString() encodedAuthData: string; diff --git a/src/modules/auth/dto/refresh.dto.ts b/src/modules/auth/dto/refresh.dto.ts index a8bf347..a2e7330 100644 --- a/src/modules/auth/dto/refresh.dto.ts +++ b/src/modules/auth/dto/refresh.dto.ts @@ -1,7 +1,7 @@ -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsNotEmpty, IsString } from "class-validator"; export class RefreshDto { - @IsNotEmpty({ message: 'Token not provided' }) + @IsNotEmpty({ message: "Token not provided" }) @IsString() token: string; } diff --git a/src/modules/auth/dto/register.dto.ts b/src/modules/auth/dto/register.dto.ts index f640582..cc183c0 100644 --- a/src/modules/auth/dto/register.dto.ts +++ b/src/modules/auth/dto/register.dto.ts @@ -7,36 +7,40 @@ import { IsString, MaxLength, MinLength, -} from 'class-validator'; +} from "class-validator"; -import { AuthSource, authSources } from '../auth.types'; +import { AuthSource, authSources } from "../auth.types"; export class RegisterUserDto { @IsString() - @IsNotEmpty({ message: 'Username not be provided' }) - @MinLength(5, { message: 'Provided username not up to the required length 5' }) - @MaxLength(40, { message: 'Provided length exceeds required length of 40' }) + @IsNotEmpty({ message: "Username not be provided" }) + @MinLength(5, { + message: "Provided username not up to the required length 5", + }) + @MaxLength(40, { message: "Provided length exceeds required length of 40" }) username: string; - @IsNotEmpty({ message: 'Email is required' }) - @IsEmail({}, { message: 'Valid email is required' }) + @IsNotEmpty({ message: "Email is required" }) + @IsEmail({}, { message: "Valid email is required" }) emailAddress: string; @IsString() - @IsNotEmpty({ message: 'First name not be provided' }) + @IsNotEmpty({ message: "First name not be provided" }) firstName: string; @IsString() - @IsNotEmpty({ message: 'Last name not provided' }) + @IsNotEmpty({ message: "Last name not provided" }) lastName: string; @IsString() - @IsNotEmpty({ message: 'Auth source not provided' }) - @IsIn(authSources, { message: `authSource must be one of: ${authSources.join(', ')}` }) + @IsNotEmpty({ message: "Auth source not provided" }) + @IsIn(authSources, { + message: `authSource must be one of: ${authSources.join(", ")}`, + }) authSource: AuthSource; @IsString() - @IsNotEmpty({ message: 'Auth data not provided' }) + @IsNotEmpty({ message: "Auth data not provided" }) encodedAuthData: string; @IsOptional() diff --git a/src/modules/auth/dto/wslogin.dto.ts b/src/modules/auth/dto/wslogin.dto.ts index 87c036d..bba5e02 100644 --- a/src/modules/auth/dto/wslogin.dto.ts +++ b/src/modules/auth/dto/wslogin.dto.ts @@ -1,11 +1,11 @@ -import { IsNotEmpty, IsNumber } from 'class-validator'; +import { IsNotEmpty, IsNumber } from "class-validator"; export class WSLoginDto { - @IsNotEmpty({ message: 'project Id not provided' }) + @IsNotEmpty({ message: "project Id not provided" }) @IsNumber() projectId: number; - @IsNotEmpty({ message: 'collection Id not provided' }) + @IsNotEmpty({ message: "collection Id not provided" }) @IsNumber() collectionId: number; } diff --git a/src/modules/auth/firebase/index.ts b/src/modules/auth/firebase/index.ts index 2d9bacc..f7225e7 100644 --- a/src/modules/auth/firebase/index.ts +++ b/src/modules/auth/firebase/index.ts @@ -1,9 +1,9 @@ -import crypto, { createHash } from 'crypto'; +import crypto, { createHash } from "crypto"; -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { GCP } from '../../configuration/config.interface'; -import { ConfigService } from '../../configuration/config.service'; +import { GCP } from "../../configuration/config.interface"; +import { ConfigService } from "../../configuration/config.service"; let keysCache: { keys: Record; expiry: number } | null = null; @@ -17,14 +17,14 @@ export class FirebaseJWTProvider { } const keysResponse = await fetch( - 'https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com', + "https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com", ); if (!keysResponse.ok) { - throw new Error('Failed to fetch Firebase JWT keys'); + throw new Error("Failed to fetch Firebase JWT keys"); } - const cacheControl = keysResponse.headers.get('cache-control'); + const cacheControl = keysResponse.headers.get("cache-control"); const maxAge = cacheControl?.match(/max-age=(\d+)/)?.[1]; const keys = (await keysResponse.json()) as Record; @@ -44,7 +44,7 @@ export class FirebaseJWTProvider { return false; } - const parts = idToken.split('.'); + const parts = idToken.split("."); if (parts.length !== 3) { return false; } @@ -52,15 +52,20 @@ export class FirebaseJWTProvider { const [encodedHeader, encodedPayload, signature] = parts; try { - const header = JSON.parse(Buffer.from(encodedHeader, 'base64url').toString()); + const header = JSON.parse( + Buffer.from(encodedHeader, "base64url").toString(), + ); - const payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString()); + const payload = JSON.parse( + Buffer.from(encodedPayload, "base64url").toString(), + ); - if (header.alg !== 'RS256' || typeof header.kid !== 'string') { + if (header.alg !== "RS256" || typeof header.kid !== "string") { return false; } - const gcpProjectId = this.configService.getTransformed('gcp').projectId; + const gcpProjectId = + this.configService.getTransformed("gcp").projectId; const nowSeconds = Date.now() / 1000; if ( @@ -83,18 +88,18 @@ export class FirebaseJWTProvider { return false; } - const verifier = crypto.createVerify('sha256'); + const verifier = crypto.createVerify("sha256"); verifier.update(`${encodedHeader}.${encodedPayload}`); - return verifier.verify(key, signature, 'base64'); + return verifier.verify(key, signature, "base64"); } catch { return false; } } static getFcmGroupId(projectId: number, collectionId: number): string { - return createHash('sha256') + return createHash("sha256") .update(`collection-sync/${projectId}/${collectionId}`) - .digest('hex') + .digest("hex") .toString(); } } diff --git a/src/modules/auth/index.ts b/src/modules/auth/index.ts index 4583378..7781688 100644 --- a/src/modules/auth/index.ts +++ b/src/modules/auth/index.ts @@ -1,4 +1,4 @@ -export { AuthService } from './auth.service'; -export { FirebaseJWTProvider } from './firebase/index'; -export type { AuthSource } from './auth.types'; -export { AuthController } from './auth.controller'; +export { AuthService } from "./auth.service"; +export { FirebaseJWTProvider } from "./firebase/index"; +export type { AuthSource } from "./auth.types"; +export { AuthController } from "./auth.controller"; diff --git a/src/modules/billing/billing.controller.ts b/src/modules/billing/billing.controller.ts index e6579d2..6701b8d 100644 --- a/src/modules/billing/billing.controller.ts +++ b/src/modules/billing/billing.controller.ts @@ -1,20 +1,20 @@ -import { Body, Get, JsonController, Param, Post } from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +import { Body, Get, JsonController, Param, Post } from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { Subscription } from '../repositories/billing'; -import { PlanSummaryDto } from '../repositories/projects'; -import { JsonResponse } from '../shared'; +import { Subscription } from "../repositories/billing"; +import { PlanSummaryDto } from "../repositories/projects"; +import { JsonResponse } from "../shared"; -import { BillingSubscriptionResponse } from './billing.interface'; -import { BillingService } from './billing.service'; -import { GeneratePaymentLinkDto } from './dto/generate-payment-link.dto'; +import { BillingSubscriptionResponse } from "./billing.interface"; +import { BillingService } from "./billing.service"; +import { GeneratePaymentLinkDto } from "./dto/generate-payment-link.dto"; @Service() -@JsonController('/billing') +@JsonController("/billing") export class BillingController { constructor(@Inject() private readonly billingService: BillingService) {} - @Post('/upgrade') + @Post("/upgrade") async create( @Body({ validate: true }) payload: GeneratePaymentLinkDto, ): Promise> { @@ -25,32 +25,32 @@ export class BillingController { payload.postalCode, ); return { - status: 'successful', - message: 'Subscription plan upgraded successfully', + status: "successful", + message: "Subscription plan upgraded successfully", data: { id, }, }; } - @Get('/plans') + @Get("/plans") async getPlans(): Promise> { const data = await this.billingService.getPlans(); return { - status: 'successful', - message: 'Plans successfully retrieved', + status: "successful", + message: "Plans successfully retrieved", data, }; } - @Get('/active-subscription/:projectId') + @Get("/active-subscription/:projectId") async getActiveSubscription( - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, ): Promise> { const data = await this.billingService.getActiveSubscriptionPlan(projectId); return { - status: 'successful', - message: 'Plans successfully retrieved', + status: "successful", + message: "Plans successfully retrieved", data, }; } diff --git a/src/modules/billing/billing.interface.ts b/src/modules/billing/billing.interface.ts index 9dc2bcb..e3c10a3 100644 --- a/src/modules/billing/billing.interface.ts +++ b/src/modules/billing/billing.interface.ts @@ -1,4 +1,4 @@ -import { SubscriptionPlan } from '../shared/types/subscriptions'; +import { SubscriptionPlan } from "../shared/types/subscriptions"; export interface BillingSubscriptionRequest { plan: SubscriptionPlan; diff --git a/src/modules/billing/billing.service.ts b/src/modules/billing/billing.service.ts index ec698bb..3acd7b2 100644 --- a/src/modules/billing/billing.service.ts +++ b/src/modules/billing/billing.service.ts @@ -1,20 +1,20 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { createHmac, timingSafeEqual } from 'crypto'; +import { createHmac, timingSafeEqual } from "crypto"; -import { EventName } from '@paddle/paddle-node-sdk'; -import { Inject, Service } from 'typedi'; +import { EventName } from "@paddle/paddle-node-sdk"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ConfigService, Paddle as PaddleConfig } from '../configuration'; -import { ApplicationError, applicationErrorCodes } from '../errors'; -import { PaddleHttpClient } from '../http/paddle-http-client'; -import { MailgunSender, MailTemplate } from '../mailing'; -import { BillingRepository, Subscription } from '../repositories/billing'; -import { PlanSummaryDto, ProjectRepository } from '../repositories/projects'; -import { InvoicePaymentStatus, InvoiceStatus } from '../shared/types/invoice'; -import { SubscriptionStatus } from '../shared/types/subscriptions'; +import { ConfigService, Paddle as PaddleConfig } from "../configuration"; +import { ApplicationError, applicationErrorCodes } from "../errors"; +import { PaddleHttpClient } from "../http/paddle-http-client"; +import { MailgunSender, MailTemplate } from "../mailing"; +import { BillingRepository, Subscription } from "../repositories/billing"; +import { PlanSummaryDto, ProjectRepository } from "../repositories/projects"; +import { InvoicePaymentStatus, InvoiceStatus } from "../shared/types/invoice"; +import { SubscriptionStatus } from "../shared/types/subscriptions"; @Service() export class BillingService { @@ -37,25 +37,28 @@ export class BillingService { const project = await this.projectRepository.getProjectById(projectId); if (project === null) { throw new ApplicationError({ - message: 'Project not found', + message: "Project not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } - const projectUsers = await this.projectRepository.getProjectUsers(projectId); + const projectUsers = + await this.projectRepository.getProjectUsers(projectId); if (projectUsers === null) { throw new ApplicationError({ - message: 'Project users not found', + message: "Project users not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } // get project user - const adminUser = projectUsers?.users.find((user) => user.permissions.includes('admin')); + const adminUser = projectUsers?.users.find((user) => + user.permissions.includes("admin"), + ); if (adminUser === null) { throw new ApplicationError({ - message: 'Project admin user not found', + message: "Project admin user not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); @@ -65,7 +68,7 @@ export class BillingService { const plan = await this.billingRepository.getPlan(planId); if (plan == null) { throw new ApplicationError({ - message: 'Plan not found', + message: "Plan not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); @@ -73,7 +76,10 @@ export class BillingService { // check if project already has an unpaid invoice for this plan // or has an active subscription on this plan const existingSubscription = - await this.billingRepository.getProjectSubscriptionByProjectIdAndPlanId(projectId, planId); + await this.billingRepository.getProjectSubscriptionByProjectIdAndPlanId( + projectId, + planId, + ); if (existingSubscription !== null) { const paddleTransactionId = await this.handleWhenSubscriptionExistWithFailedPaymentOrUnpaidInvoice( @@ -85,18 +91,19 @@ export class BillingService { } // get project previous subscription - const previousSubscription = await this.billingRepository.getProjectLastSubscription(projectId); + const previousSubscription = + await this.billingRepository.getProjectLastSubscription(projectId); await this.billingRepository.getProjectActiveSubscription(projectId); if (!previousSubscription) { throw new ApplicationError({ - message: 'Project does not have an active subscription', + message: "Project does not have an active subscription", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } if (previousSubscription.planId === planId) { throw new ApplicationError({ - message: 'User already on selected plan, please choose another plan', + message: "User already on selected plan, please choose another plan", logger: this.logger, errorCode: applicationErrorCodes.invalidRequest, }); @@ -109,7 +116,7 @@ export class BillingService { previousSubscription, }, invoiceRef, - 'paddle', + "paddle", invoiceRef, ); @@ -127,7 +134,7 @@ export class BillingService { invoice_id: subscriptionSetUp!.invoiceId, subscription_id: subscriptionSetUp!.subscriptionId, invoice_payment_id: subscriptionSetUp!.invoicePaymentId, - bonadocs_type: 'new_subscription', + bonadocs_type: "new_subscription", } as Record, }, transaction: { @@ -157,7 +164,8 @@ export class BillingService { } async getActiveSubscriptionPlan(projectId: number): Promise { - const activeSubscription = await this.billingRepository.getProjectActiveSubscription(projectId); + const activeSubscription = + await this.billingRepository.getProjectActiveSubscription(projectId); if (!activeSubscription) { throw new ApplicationError({ message: `Project with id ${projectId} has not active subscription`, @@ -172,42 +180,50 @@ export class BillingService { // handle new subscription charge try { const transactionId: string = data.id; - const transaction = await this.paddleHttpClient.getTransactionById(transactionId); + const transaction = + await this.paddleHttpClient.getTransactionById(transactionId); const origin = data?.origin || null; const bonadocsType = data.custom_data.bonadocs_type; const invoiceId = Number(transaction!.custom_data.invoice_id); - const invoicePaymentId = Number(transaction!.custom_data.invoice_payment_id); + const invoicePaymentId = Number( + transaction!.custom_data.invoice_payment_id, + ); // check if invoicePayment is valid and has been handled - const invoicePayment = await this.billingRepository.getInvoicePaymentById(invoicePaymentId); + const invoicePayment = + await this.billingRepository.getInvoicePaymentById(invoicePaymentId); if (invoicePayment === null) { throw new ApplicationError({ - message: 'Invoice payment not found', + message: "Invoice payment not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } if (invoicePayment!.status === InvoicePaymentStatus.SUCCESSFUL) { - this.logger.info(`Invoice payment with id ${invoicePaymentId} has already been handled`); + this.logger.info( + `Invoice payment with id ${invoicePaymentId} has already been handled`, + ); return; } const subscriptionId = Number(transaction!.custom_data.subscription_id); if ( - (origin === 'api' && bonadocsType === 'new_subscription') || - bonadocsType === 'new_subscription' + (origin === "api" && bonadocsType === "new_subscription") || + bonadocsType === "new_subscription" ) { const paddleSubscriptionId = transaction.subscription_id; let updatedMetadata: Record | null = null; // get new subscription - const newSubscription = await this.billingRepository.getSubscriptionById(subscriptionId); + const newSubscription = + await this.billingRepository.getSubscriptionById(subscriptionId); if (newSubscription === null) { throw new ApplicationError({ - message: 'Subscription not found', + message: "Subscription not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } - const activeSubscriptionId = newSubscription.metadata.previousSubscriptionId; + const activeSubscriptionId = + newSubscription.metadata.previousSubscriptionId; if (paddleSubscriptionId) { updatedMetadata = { ...newSubscription.metadata, @@ -226,22 +242,24 @@ export class BillingService { ); } - if (origin === 'subscription_recurring') { + if (origin === "subscription_recurring") { const paddleTransactionId = data.id; let updatedMetadata: Record | null = null; // get new subscription - const newSubscription = await this.billingRepository.getSubscriptionById( - Number(subscriptionId), - ); + const newSubscription = + await this.billingRepository.getSubscriptionById( + Number(subscriptionId), + ); if (newSubscription === null) { throw new ApplicationError({ - message: 'Subscription not found', + message: "Subscription not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } - const activeSubscriptionId = newSubscription.metadata.previousSubscriptionId; + const activeSubscriptionId = + newSubscription.metadata.previousSubscriptionId; if (paddleTransactionId) { updatedMetadata = { ...newSubscription.metadata, @@ -260,7 +278,7 @@ export class BillingService { ); } - if (origin === 'subscription_charge') { + if (origin === "subscription_charge") { await this.billingRepository.updateTransactionPaidForSubscriptionOverage( invoiceId, InvoiceStatus.PAID, @@ -269,9 +287,9 @@ export class BillingService { ); } } catch (error) { - this.logger.error('Error handling transaction paid webhook', error); + this.logger.error("Error handling transaction paid webhook", error); throw new ApplicationError({ - message: 'Error handling transaction paid webhook', + message: "Error handling transaction paid webhook", logger: this.logger, errorCode: applicationErrorCodes.invalidTransactionData, }); @@ -280,18 +298,22 @@ export class BillingService { async handleTransactionPaymentFailedWebhook(): Promise {} - async handleSubscriptionCreatedWebhook(data: Record): Promise { + async handleSubscriptionCreatedWebhook( + data: Record, + ): Promise { const transactionId = data.id; - const transaction = await this.paddleHttpClient.getTransactionById(transactionId); + const transaction = + await this.paddleHttpClient.getTransactionById(transactionId); const subscriptionId = Number(transaction!.custom_data.subscription_id); const paddleSubscriptionId = data.id; let updatedMetadata: Record = {}; // get new subscription - const newSubscription = await this.billingRepository.getSubscriptionById(subscriptionId); + const newSubscription = + await this.billingRepository.getSubscriptionById(subscriptionId); if (newSubscription === null) { throw new ApplicationError({ - message: 'Subscription not found', + message: "Subscription not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); @@ -302,7 +324,10 @@ export class BillingService { paddleSubscriptionId, }; } - await this.billingRepository.updateSubscriptionMetadata(subscriptionId, updatedMetadata); + await this.billingRepository.updateSubscriptionMetadata( + subscriptionId, + updatedMetadata, + ); } async handlerOverageCharge(projectId: number): Promise { @@ -310,14 +335,15 @@ export class BillingService { const project = await this.projectRepository.getProjectById(projectId); if (project === null) { throw new ApplicationError({ - message: 'Project not found', + message: "Project not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } // check if project has an active subscription let hasActiveSubscription: boolean = true; - let subscription = await this.billingRepository.getProjectActiveSubscription(projectId); + let subscription = + await this.billingRepository.getProjectActiveSubscription(projectId); if (!subscription) { throw new ApplicationError({ message: `Subscription with project id ${projectId} not found`, @@ -332,7 +358,8 @@ export class BillingService { // handle if project has active subscription if (subscription) { - subscription = await this.billingRepository.getProjectLastSubscription(projectId); + subscription = + await this.billingRepository.getProjectLastSubscription(projectId); hasActiveSubscription = true; } const invoiceRef = this.generateInvoiceRef(); @@ -340,14 +367,14 @@ export class BillingService { subscriptionId: subscription!.id, reference: invoiceRef, amount, - currency: 'USD', + currency: "USD", status: InvoiceStatus.UNPAID, dateCreated: new Date(), dueDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), // how do we determine this? }); if (!invoiceId) { throw new ApplicationError({ - message: 'Failed to create invoice', + message: "Failed to create invoice", logger: this.logger, errorCode: applicationErrorCodes.invalidTransactionData, }); @@ -355,66 +382,75 @@ export class BillingService { const invoicePaymentId = await this.billingRepository.createInvoicePayment({ invoiceId, amount, - paymentChannel: 'paddle', + paymentChannel: "paddle", paymentReference: invoiceRef, - currency: 'USD', + currency: "USD", status: InvoicePaymentStatus.PENDING, dateCreated: new Date(), }); if (!invoicePaymentId) { throw new ApplicationError({ - message: 'Failed to create invoice payment', + message: "Failed to create invoice payment", logger: this.logger, errorCode: applicationErrorCodes.invalidTransactionData, }); } // create paddle overage charge - const response = await this.paddleHttpClient.createOneTimeChargeOnSubscription( - subscription!.metadata.subscriptionId, - { - effective_from: hasActiveSubscription ? 'next_billing_period' : 'immediately', - item: [ - { - price: { - product_id: plan?.metadata.paddleData.productId, - description: 'Subscription Overage Charge', - unit_price: { - amount, - currency: 'USD', - }, - quantity: 1, - custom_data: { - invoice_id: invoiceId, - invoice_payment_id: invoicePaymentId, + const response = + await this.paddleHttpClient.createOneTimeChargeOnSubscription( + subscription!.metadata.subscriptionId, + { + effective_from: hasActiveSubscription + ? "next_billing_period" + : "immediately", + item: [ + { + price: { + product_id: plan?.metadata.paddleData.productId, + description: "Subscription Overage Charge", + unit_price: { + amount, + currency: "USD", + }, + quantity: 1, + custom_data: { + invoice_id: invoiceId, + invoice_payment_id: invoicePaymentId, + }, }, }, + ], + custom_data: { + invoice_id: invoiceId, + invoice_payment_id: invoicePaymentId, }, - ], - custom_data: { - invoice_id: invoiceId, - invoice_payment_id: invoicePaymentId, }, - }, - ); + ); if (!hasActiveSubscription) { // try to get transactions asn send checkout link to user in mail - const transactions = await this.paddleHttpClient.getTransactionsBySubscriptionId(response.id); + const transactions = + await this.paddleHttpClient.getTransactionsBySubscriptionId( + response.id, + ); const checkoutUrl = transactions[0].checkout.url; - const projectUsers = await this.projectRepository.getProjectUsers(projectId); + const projectUsers = + await this.projectRepository.getProjectUsers(projectId); if (projectUsers === null) { throw new ApplicationError({ - message: 'Project users not found', + message: "Project users not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } // get project user - const adminUser = projectUsers?.users.find((user) => user.permissions.includes('admin')); + const adminUser = projectUsers?.users.find((user) => + user.permissions.includes("admin"), + ); if (adminUser === null) { throw new ApplicationError({ - message: 'Project admin user not found', + message: "Project admin user not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); @@ -424,7 +460,7 @@ export class BillingService { receiver: { to: adminUser!.emailAddress, }, - subject: 'Bonadocs Overage Charge', + subject: "Bonadocs Overage Charge", template: MailTemplate.OveragesNotification, data: { paymentLink: checkoutUrl, @@ -433,9 +469,12 @@ export class BillingService { } // update invoice payment metadata - await this.billingRepository.updateInvoicePaymentMetadata(invoicePaymentId, { - paddleTransactionId: response.id, - }); + await this.billingRepository.updateInvoicePaymentMetadata( + invoicePaymentId, + { + paddleTransactionId: response.id, + }, + ); } async handleEvent(eventData: Record): Promise { @@ -461,14 +500,15 @@ export class BillingService { async verifyWebhookSignature( request: Record, ): Promise> | null> { - const paddleConfigs = this.configService.getTransformed('paddle'); + const paddleConfigs = + this.configService.getTransformed("paddle"); if (!Buffer.isBuffer(request.body)) { return null; } // 1. Get Paddle-Signature header - const paddleSignature = request.headers['paddle-signature'] as string; + const paddleSignature = request.headers["paddle-signature"] as string; const secretKey = paddleConfigs.webhookSecretKey; // (Optional) Check if header and secret key are present and return error if not @@ -481,16 +521,18 @@ export class BillingService { } // 2. Extract timestamp and signature from header - if (!paddleSignature || !paddleSignature.includes(';')) { + if (!paddleSignature || !paddleSignature.includes(";")) { return null; } - const parts = paddleSignature.split(';'); + const parts = paddleSignature.split(";"); if (parts.length !== 2) { return null; } - const [timestampPart, signaturePart] = parts.map((part) => part.split('=')[1]); + const [timestampPart, signaturePart] = parts.map( + (part) => part.split("=")[1], + ); if (!timestampPart || !signaturePart) { return null; @@ -518,9 +560,9 @@ export class BillingService { const signedPayload = `${timestamp}:${bodyRaw}`; // 4. Hash signed payload using HMAC SHA256 and the secret key - const hashedPayload = createHmac('sha256', secretKey) - .update(signedPayload, 'utf8') - .digest('hex'); + const hashedPayload = createHmac("sha256", secretKey) + .update(signedPayload, "utf8") + .digest("hex"); // 5. Compare signatures if (!timingSafeEqual(Buffer.from(hashedPayload), Buffer.from(signature))) { @@ -531,20 +573,20 @@ export class BillingService { generateInvoiceRef(): string { const now = new Date(); - const pad2 = (n: number) => n.toString().padStart(2, '0'); + const pad2 = (n: number) => n.toString().padStart(2, "0"); const year = now.getFullYear(); const month = pad2(now.getMonth() + 1); const day = pad2(now.getDate()); const hour = pad2(now.getHours()); const minute = pad2(now.getMinutes()); const second = pad2(now.getSeconds()); - const milli = now.getMilliseconds().toString().padStart(3, '0'); + const milli = now.getMilliseconds().toString().padStart(3, "0"); const timestamp = `${year}${month}${day}${hour}${minute}${second}${milli}`; return `INV-${timestamp}`; } private bufferToEventyEntity(buffer: Buffer): Record { - const decoder = new TextDecoder('utf-8'); + const decoder = new TextDecoder("utf-8"); const jsonString = decoder.decode(new Uint8Array(buffer)); return JSON.parse(jsonString) as Record; } @@ -554,18 +596,22 @@ export class BillingService { ): Promise { if (existingSubscription?.status === SubscriptionStatus.Active) { throw new ApplicationError({ - message: 'Project already has an active subscription on this plan', + message: "Project already has an active subscription on this plan", logger: this.logger, errorCode: applicationErrorCodes.invalidRequest, }); } // check if invoice is paid but not yet set up - const existingSubscriptionInvoice = await this.billingRepository.getInvoiceBySubscriptionId( - existingSubscription!.id, - ); - if (existingSubscriptionInvoice && existingSubscriptionInvoice.status === InvoiceStatus.PAID) { + const existingSubscriptionInvoice = + await this.billingRepository.getInvoiceBySubscriptionId( + existingSubscription!.id, + ); + if ( + existingSubscriptionInvoice && + existingSubscriptionInvoice.status === InvoiceStatus.PAID + ) { throw new ApplicationError({ - message: 'Project already has an active subscription on this plan', + message: "Project already has an active subscription on this plan", logger: this.logger, errorCode: applicationErrorCodes.invalidRequest, }); @@ -574,7 +620,10 @@ export class BillingService { const invoicePayment = await this.billingRepository.getInvoicePaymentById( existingSubscriptionInvoice.id, ); - if (invoicePayment && invoicePayment.status === InvoicePaymentStatus.PENDING) { + if ( + invoicePayment && + invoicePayment.status === InvoicePaymentStatus.PENDING + ) { return existingSubscription!.metadata.paddleTransactionId; } } diff --git a/src/modules/billing/dto/generate-payment-link.dto.ts b/src/modules/billing/dto/generate-payment-link.dto.ts index 4e6f2f6..ea2dbd8 100644 --- a/src/modules/billing/dto/generate-payment-link.dto.ts +++ b/src/modules/billing/dto/generate-payment-link.dto.ts @@ -1,23 +1,23 @@ -import { IsNotEmpty } from 'class-validator'; +import { IsNotEmpty } from "class-validator"; export class GeneratePaymentLinkDto { @IsNotEmpty({ - message: 'Subscription plan not provided', + message: "Subscription plan not provided", }) planId: number; @IsNotEmpty({ - message: 'Project Id not provided', + message: "Project Id not provided", }) projectId: number; @IsNotEmpty({ - message: 'Country code not provided', + message: "Country code not provided", }) countryCode: string; @IsNotEmpty({ - message: 'Postal code not provided', + message: "Postal code not provided", }) postalCode: string; } diff --git a/src/modules/billing/index.ts b/src/modules/billing/index.ts index 7c0ae7a..8fc233a 100644 --- a/src/modules/billing/index.ts +++ b/src/modules/billing/index.ts @@ -1 +1 @@ -export { BillingController } from './billing.controller'; +export { BillingController } from "./billing.controller"; diff --git a/src/modules/blockscan/etherscan/index.ts b/src/modules/blockscan/etherscan/index.ts index 43f3422..2fd3950 100644 --- a/src/modules/blockscan/etherscan/index.ts +++ b/src/modules/blockscan/etherscan/index.ts @@ -1,13 +1,13 @@ -import qs from 'querystring'; +import qs from "querystring"; -import axios from 'axios'; +import axios from "axios"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { Blockscan } from '../../configuration/config.interface'; -import { getConfigService } from '../../configuration/config.service'; -import { supportedChains } from '../supported-chains'; -import { configureThrottle, throttleWrap } from '../throttler'; +import { Blockscan } from "../../configuration/config.interface"; +import { getConfigService } from "../../configuration/config.service"; +import { supportedChains } from "../supported-chains"; +import { configureThrottle, throttleWrap } from "../throttler"; const etherscanThrottleConfig = { delayMs: 1000, @@ -20,7 +20,8 @@ export async function getVerifiedABI( chainId: number, contractAddress: string, ): Promise { - const configuration = getConfigService().getTransformed('blockscan'); + const configuration = + getConfigService().getTransformed("blockscan"); const throttleId = `etherscan:${chainId}`; configureThrottle(logger, throttleId, etherscanThrottleConfig); const apiKey = configuration.etherscan[`evm:${chainId}`]; @@ -32,7 +33,7 @@ export async function getVerifiedABI( const apiUrl = supportedChains .get(chainId) - ?.specApiConfigs.find((c) => c.specApiType === 'etherscan')?.specApiUrl; + ?.specApiConfigs.find((c) => c.specApiType === "etherscan")?.specApiUrl; if (!apiUrl) { logger.error(`getVerifiedABI::chain not supported::${chainId}`); return null; @@ -40,8 +41,8 @@ export async function getVerifiedABI( try { const queryParams: Record = { - module: 'contract', - action: 'getabi', + module: "contract", + action: "getabi", address: contractAddress, apikey: apiKey, }; @@ -50,8 +51,10 @@ export async function getVerifiedABI( } const params = qs.stringify(queryParams); - const response = await throttleWrap(throttleId, () => axios.get(`${apiUrl}/api?${params}`)); - if (response.data.message !== 'OK') { + const response = await throttleWrap(throttleId, () => + axios.get(`${apiUrl}/api?${params}`), + ); + if (response.data.message !== "OK") { return null; } diff --git a/src/modules/blockscan/index.ts b/src/modules/blockscan/index.ts index ec0c395..6299058 100644 --- a/src/modules/blockscan/index.ts +++ b/src/modules/blockscan/index.ts @@ -1,10 +1,10 @@ -import { JsonRpcProvider } from 'ethers'; +import { JsonRpcProvider } from "ethers"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import * as etherscan from './etherscan/index'; -import { detectProxyTarget } from './proxy-detection/index'; -import { supportedChains } from './supported-chains'; +import * as etherscan from "./etherscan/index"; +import { detectProxyTarget } from "./proxy-detection/index"; +import { supportedChains } from "./supported-chains"; export async function getVerifiedContractABI( logger: BonadocsLogger, @@ -27,8 +27,9 @@ export async function getVerifiedContractABI( } const jsonRpcProvider = new JsonRpcProvider(chain.jsonRpcUrl); - const proxyTarget = await detectProxyTarget(contractAddress, ({ method, params }) => - jsonRpcProvider.send(method, params), + const proxyTarget = await detectProxyTarget( + contractAddress, + ({ method, params }) => jsonRpcProvider.send(method, params), ); if (!proxyTarget) { return contractSpec; @@ -40,8 +41,13 @@ export async function getVerifiedContractABI( chainId, proxyTarget, ); - const implementationContractSpec = JSON.parse(implementationContractSpecString || '[]'); - const mergedContractSpec = [...proxyContractSpec, ...implementationContractSpec]; + const implementationContractSpec = JSON.parse( + implementationContractSpecString || "[]", + ); + const mergedContractSpec = [ + ...proxyContractSpec, + ...implementationContractSpec, + ]; return JSON.stringify(mergedContractSpec); } @@ -52,7 +58,7 @@ async function loadContractSpecForSupportedChain( contractAddress: string, ) { switch (blockscanType) { - case 'etherscan': + case "etherscan": return etherscan.getVerifiedABI(logger, chainId, contractAddress); default: return null; diff --git a/src/modules/blockscan/proxy-detection/index.ts b/src/modules/blockscan/proxy-detection/index.ts index c4a8392..cea7f27 100644 --- a/src/modules/blockscan/proxy-detection/index.ts +++ b/src/modules/blockscan/proxy-detection/index.ts @@ -1,4 +1,4 @@ -type BlockTag = number | 'earliest' | 'latest' | 'pending'; +type BlockTag = number | "earliest" | "latest" | "pending"; interface RequestArguments { method: string; @@ -8,46 +8,49 @@ interface RequestArguments { type EIP1193ProviderRequestFunc = (args: RequestArguments) => Promise; // obtained as bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) -const EIP_1967_LOGIC_SLOT = '0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc'; +const EIP_1967_LOGIC_SLOT = + "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; // obtained as bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1) -const EIP_1967_BEACON_SLOT = '0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50'; +const EIP_1967_BEACON_SLOT = + "0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50"; // obtained as keccak256("org.zeppelinos.proxy.implementation") const OPEN_ZEPPELIN_IMPLEMENTATION_SLOT = - '0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3'; + "0x7050c9e0f4ca769c69bd3a8ef740bc37934f8e2c036e5a723fd8ee048ed3f8c3"; // obtained as keccak256("PROXIABLE") -const EIP_1822_LOGIC_SLOT = '0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7'; +const EIP_1822_LOGIC_SLOT = + "0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7"; const EIP_1167_BEACON_METHODS = [ // bytes4(keccak256("implementation()")) padded to 32 bytes - '0x5c60da1b00000000000000000000000000000000000000000000000000000000', + "0x5c60da1b00000000000000000000000000000000000000000000000000000000", // bytes4(keccak256("childImplementation()")) padded to 32 bytes // some implementations use this over the standard method name so // that the beacon contract is not detected as an EIP-897 proxy itself - '0xda52571600000000000000000000000000000000000000000000000000000000', + "0xda52571600000000000000000000000000000000000000000000000000000000", ]; const EIP_897_INTERFACE = [ // bytes4(keccak256("implementation()")) padded to 32 bytes - '0x5c60da1b00000000000000000000000000000000000000000000000000000000', + "0x5c60da1b00000000000000000000000000000000000000000000000000000000", ]; const GNOSIS_SAFE_PROXY_INTERFACE = [ // bytes4(keccak256("masterCopy()")) padded to 32 bytes - '0xa619486e00000000000000000000000000000000000000000000000000000000', + "0xa619486e00000000000000000000000000000000000000000000000000000000", ]; const COMPTROLLER_PROXY_INTERFACE = [ // bytes4(keccak256("comptrollerImplementation()")) padded to 32 bytes - '0xbb82aa5e00000000000000000000000000000000000000000000000000000000', + "0xbb82aa5e00000000000000000000000000000000000000000000000000000000", ]; const detectProxyTarget = ( proxyAddress: string, jsonRpcRequest: EIP1193ProviderRequestFunc, - blockTag: BlockTag = 'latest', + blockTag: BlockTag = "latest", ): Promise => Promise.any([ // timeout - 3 seconds @@ -56,7 +59,7 @@ const detectProxyTarget = ( }), // EIP-1167 Minimal Proxy Contract jsonRpcRequest({ - method: 'eth_getCode', + method: "eth_getCode", params: [proxyAddress, blockTag], }) .then(parse1167Bytecode) @@ -64,19 +67,19 @@ const detectProxyTarget = ( // EIP-1967 direct proxy jsonRpcRequest({ - method: 'eth_getStorageAt', + method: "eth_getStorageAt", params: [proxyAddress, EIP_1967_LOGIC_SLOT, blockTag], }).then(readAddress), // EIP-1967 beacon proxy jsonRpcRequest({ - method: 'eth_getStorageAt', + method: "eth_getStorageAt", params: [proxyAddress, EIP_1967_BEACON_SLOT, blockTag], }) .then(readAddress) .then((beaconAddress) => jsonRpcRequest({ - method: 'eth_call', + method: "eth_call", params: [ { to: beaconAddress, @@ -86,7 +89,7 @@ const detectProxyTarget = ( ], }).catch(() => jsonRpcRequest({ - method: 'eth_call', + method: "eth_call", params: [ { to: beaconAddress, @@ -101,19 +104,19 @@ const detectProxyTarget = ( // OpenZeppelin proxy pattern jsonRpcRequest({ - method: 'eth_getStorageAt', + method: "eth_getStorageAt", params: [proxyAddress, OPEN_ZEPPELIN_IMPLEMENTATION_SLOT, blockTag], }).then(readAddress), // EIP-1822 Universal Upgradeable Proxy Standard jsonRpcRequest({ - method: 'eth_getStorageAt', + method: "eth_getStorageAt", params: [proxyAddress, EIP_1822_LOGIC_SLOT, blockTag], }).then(readAddress), // EIP-897 DelegateProxy pattern jsonRpcRequest({ - method: 'eth_call', + method: "eth_call", params: [ { to: proxyAddress, @@ -125,7 +128,7 @@ const detectProxyTarget = ( // GnosisSafeProxy contract jsonRpcRequest({ - method: 'eth_call', + method: "eth_call", params: [ { to: proxyAddress, @@ -137,7 +140,7 @@ const detectProxyTarget = ( // Comptroller proxy jsonRpcRequest({ - method: 'eth_call', + method: "eth_call", params: [ { to: proxyAddress, @@ -149,7 +152,7 @@ const detectProxyTarget = ( ]).catch(() => null); const readAddress = (value: unknown): string => { - if (typeof value !== 'string' || value === '0x') { + if (typeof value !== "string" || value === "0x") { throw new Error(`Invalid address value: ${value}`); } @@ -158,20 +161,23 @@ const readAddress = (value: unknown): string => { address = `0x${address.slice(-40)}`; } - const zeroAddress = `0x${'0'.repeat(40)}`; + const zeroAddress = `0x${"0".repeat(40)}`; if (address === zeroAddress) { - throw new Error('Empty address'); + throw new Error("Empty address"); } return address; }; -const EIP_1167_BYTECODE_PREFIX = '0x363d3d373d3d3d363d'; -const EIP_1167_BYTECODE_SUFFIX = '57fd5bf3'; +const EIP_1167_BYTECODE_PREFIX = "0x363d3d373d3d3d363d"; +const EIP_1167_BYTECODE_SUFFIX = "57fd5bf3"; const parse1167Bytecode = (bytecode: unknown): string => { - if (typeof bytecode !== 'string' || !bytecode.startsWith(EIP_1167_BYTECODE_PREFIX)) { - throw new Error('Not an EIP-1167 bytecode'); + if ( + typeof bytecode !== "string" || + !bytecode.startsWith(EIP_1167_BYTECODE_PREFIX) + ) { + throw new Error("Not an EIP-1167 bytecode"); } // detect length of address (20 bytes non-optimized, 0 < N < 20 bytes for vanity addresses) @@ -183,7 +189,7 @@ const parse1167Bytecode = (bytecode: unknown): string => { const addressLength = parseInt(pushNHex, 16) - 0x5f; if (addressLength < 1 || addressLength > 20) { - throw new Error('Not an EIP-1167 bytecode'); + throw new Error("Not an EIP-1167 bytecode"); } const addressFromBytecode = bytecode.substring( @@ -196,15 +202,18 @@ const parse1167Bytecode = (bytecode: unknown): string => { if ( !bytecode .substring( - EIP_1167_BYTECODE_PREFIX.length + 2 + addressLength * 2 + SUFFIX_OFFSET_FROM_ADDRESS_END, + EIP_1167_BYTECODE_PREFIX.length + + 2 + + addressLength * 2 + + SUFFIX_OFFSET_FROM_ADDRESS_END, ) .startsWith(EIP_1167_BYTECODE_SUFFIX) ) { - throw new Error('Not an EIP-1167 bytecode'); + throw new Error("Not an EIP-1167 bytecode"); } // padStart is needed for vanity addresses - return `0x${addressFromBytecode.padStart(40, '0')}`; + return `0x${addressFromBytecode.padStart(40, "0")}`; }; export { detectProxyTarget }; diff --git a/src/modules/blockscan/supported-chains.ts b/src/modules/blockscan/supported-chains.ts index 6ac3d88..c314378 100644 --- a/src/modules/blockscan/supported-chains.ts +++ b/src/modules/blockscan/supported-chains.ts @@ -16,11 +16,11 @@ export const supportedChains = new Map([ { chainId: 56, networkId: 56, - jsonRpcUrl: 'https://bsc-dataseed.binance.org', + jsonRpcUrl: "https://bsc-dataseed.binance.org", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.bscscan.com', + specApiType: "etherscan", + specApiUrl: "https://api.bscscan.com", }, ], }, @@ -30,11 +30,11 @@ export const supportedChains = new Map([ { chainId: 1, networkId: 1, - jsonRpcUrl: 'https://eth.llamarpc.com', + jsonRpcUrl: "https://eth.llamarpc.com", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.etherscan.io', + specApiType: "etherscan", + specApiUrl: "https://api.etherscan.io", }, ], }, @@ -44,11 +44,11 @@ export const supportedChains = new Map([ { chainId: 250, networkId: 250, - jsonRpcUrl: 'https://rpcapi.fantom.network', + jsonRpcUrl: "https://rpcapi.fantom.network", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.ftmscan.com', + specApiType: "etherscan", + specApiUrl: "https://api.ftmscan.com", }, ], }, @@ -58,11 +58,11 @@ export const supportedChains = new Map([ { chainId: 10, networkId: 10, - jsonRpcUrl: 'https://mainnet.optimism.io', + jsonRpcUrl: "https://mainnet.optimism.io", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-optimistic.etherscan.io', + specApiType: "etherscan", + specApiUrl: "https://api-optimistic.etherscan.io", }, ], }, @@ -72,11 +72,11 @@ export const supportedChains = new Map([ { chainId: 42161, networkId: 42161, - jsonRpcUrl: 'https://arb1.arbitrum.io/rpc', + jsonRpcUrl: "https://arb1.arbitrum.io/rpc", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.arbiscan.io', + specApiType: "etherscan", + specApiUrl: "https://api.arbiscan.io", }, ], }, @@ -86,11 +86,11 @@ export const supportedChains = new Map([ { chainId: 43114, networkId: 43114, - jsonRpcUrl: 'https://api.avax.network/ext/bc/C/rpc', + jsonRpcUrl: "https://api.avax.network/ext/bc/C/rpc", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.snowtrace.io', + specApiType: "etherscan", + specApiUrl: "https://api.snowtrace.io", }, ], }, @@ -100,11 +100,11 @@ export const supportedChains = new Map([ { chainId: 137, networkId: 137, - jsonRpcUrl: 'https://polygon.llamarpc.com', + jsonRpcUrl: "https://polygon.llamarpc.com", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.polygonscan.com', + specApiType: "etherscan", + specApiUrl: "https://api.polygonscan.com", }, ], }, @@ -114,11 +114,11 @@ export const supportedChains = new Map([ { chainId: 42220, networkId: 42220, - jsonRpcUrl: 'https://forno.celo.org', + jsonRpcUrl: "https://forno.celo.org", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.celoscan.io', + specApiType: "etherscan", + specApiUrl: "https://api.celoscan.io", }, ], }, @@ -128,11 +128,11 @@ export const supportedChains = new Map([ { chainId: 100, networkId: 100, - jsonRpcUrl: 'https://rpc.gnosischain.com', + jsonRpcUrl: "https://rpc.gnosischain.com", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.gnosisscan.io/', + specApiType: "etherscan", + specApiUrl: "https://api.gnosisscan.io/", }, ], }, @@ -142,11 +142,11 @@ export const supportedChains = new Map([ { chainId: 5, networkId: 5, - jsonRpcUrl: 'https://rpc.ankr.com/eth_goerli', + jsonRpcUrl: "https://rpc.ankr.com/eth_goerli", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-goerli.etherscan.io', + specApiType: "etherscan", + specApiUrl: "https://api-goerli.etherscan.io", }, ], }, @@ -156,11 +156,11 @@ export const supportedChains = new Map([ { chainId: 97, networkId: 97, - jsonRpcUrl: 'https://bsc-testnet.publicnode.com', + jsonRpcUrl: "https://bsc-testnet.publicnode.com", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-testnet.bscscan.com', + specApiType: "etherscan", + specApiUrl: "https://api-testnet.bscscan.com", }, ], }, @@ -170,11 +170,11 @@ export const supportedChains = new Map([ { chainId: 199, networkId: 199, - jsonRpcUrl: 'https://rpc.bittorrentchain.io', + jsonRpcUrl: "https://rpc.bittorrentchain.io", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.bttcscan.com', + specApiType: "etherscan", + specApiUrl: "https://api.bttcscan.com", }, ], }, @@ -184,11 +184,11 @@ export const supportedChains = new Map([ { chainId: 1101, networkId: 1101, - jsonRpcUrl: 'https://rpc.ankr.com/polygon_zkevm', + jsonRpcUrl: "https://rpc.ankr.com/polygon_zkevm", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-zkevm.polygonscan.com', + specApiType: "etherscan", + specApiUrl: "https://api-zkevm.polygonscan.com", }, ], }, @@ -198,11 +198,11 @@ export const supportedChains = new Map([ { chainId: 4002, networkId: 4002, - jsonRpcUrl: 'https://rpc.testnet.fantom.network', + jsonRpcUrl: "https://rpc.testnet.fantom.network", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-testnet.ftmscan.com', + specApiType: "etherscan", + specApiUrl: "https://api-testnet.ftmscan.com", }, ], }, @@ -212,11 +212,11 @@ export const supportedChains = new Map([ { chainId: 43113, networkId: 1, - jsonRpcUrl: 'https://api.avax-test.network/ext/bc/C/rpc', + jsonRpcUrl: "https://api.avax-test.network/ext/bc/C/rpc", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-testnet.snowtrace.io', + specApiType: "etherscan", + specApiUrl: "https://api-testnet.snowtrace.io", }, ], }, @@ -226,11 +226,11 @@ export const supportedChains = new Map([ { chainId: 44787, networkId: 44787, - jsonRpcUrl: 'https://alfajores-forno.celo-testnet.org', + jsonRpcUrl: "https://alfajores-forno.celo-testnet.org", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-alfajores.celoscan.io', + specApiType: "etherscan", + specApiUrl: "https://api-alfajores.celoscan.io", }, ], }, @@ -240,11 +240,11 @@ export const supportedChains = new Map([ { chainId: 80001, networkId: 80001, - jsonRpcUrl: 'https://polygon-mumbai-bor.publicnode.com', + jsonRpcUrl: "https://polygon-mumbai-bor.publicnode.com", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-testnet.polygonscan.com', + specApiType: "etherscan", + specApiUrl: "https://api-testnet.polygonscan.com", }, ], }, @@ -254,11 +254,11 @@ export const supportedChains = new Map([ { chainId: 421613, networkId: 421613, - jsonRpcUrl: 'https://arbitrum-goerli.publicnode.com', + jsonRpcUrl: "https://arbitrum-goerli.publicnode.com", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-goerli.arbiscan.io', + specApiType: "etherscan", + specApiUrl: "https://api-goerli.arbiscan.io", }, ], }, @@ -268,11 +268,11 @@ export const supportedChains = new Map([ { chainId: 11155111, networkId: 11155111, - jsonRpcUrl: 'https://eth-sepolia.g.alchemy.com/v2/demo', + jsonRpcUrl: "https://eth-sepolia.g.alchemy.com/v2/demo", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-sepolia.etherscan.io', + specApiType: "etherscan", + specApiUrl: "https://api-sepolia.etherscan.io", }, ], }, @@ -282,11 +282,11 @@ export const supportedChains = new Map([ { chainId: 8453, networkId: 8453, - jsonRpcUrl: 'https://base.publicnode.com', + jsonRpcUrl: "https://base.publicnode.com", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.basescan.org', + specApiType: "etherscan", + specApiUrl: "https://api.basescan.org", }, ], }, @@ -296,11 +296,11 @@ export const supportedChains = new Map([ { chainId: 1284, networkId: 1284, - jsonRpcUrl: 'https://rpc.api.moonbeam.network', + jsonRpcUrl: "https://rpc.api.moonbeam.network", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-moonbeam.moonscan.io', + specApiType: "etherscan", + specApiUrl: "https://api-moonbeam.moonscan.io", }, ], }, @@ -310,11 +310,11 @@ export const supportedChains = new Map([ { chainId: 1285, networkId: 1285, - jsonRpcUrl: 'https://rpc.api.moonriver.moonbeam.network', + jsonRpcUrl: "https://rpc.api.moonriver.moonbeam.network", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api-moonriver.moonscan.io', + specApiType: "etherscan", + specApiUrl: "https://api-moonriver.moonscan.io", }, ], }, @@ -324,11 +324,11 @@ export const supportedChains = new Map([ { chainId: 1313161554, networkId: 1313161554, - jsonRpcUrl: 'https://mainnet.aurora.dev', + jsonRpcUrl: "https://mainnet.aurora.dev", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://aurorascan.dev', + specApiType: "etherscan", + specApiUrl: "https://aurorascan.dev", }, ], }, @@ -338,7 +338,7 @@ export const supportedChains = new Map([ { chainId: 9001, networkId: 9001, - jsonRpcUrl: 'https://evmos.lava.build', + jsonRpcUrl: "https://evmos.lava.build", specApiConfigs: [], }, ], @@ -347,7 +347,7 @@ export const supportedChains = new Map([ { chainId: 1666600000, networkId: 1666600000, - jsonRpcUrl: 'https://api.harmony.one', + jsonRpcUrl: "https://api.harmony.one", specApiConfigs: [], }, ], @@ -356,11 +356,12 @@ export const supportedChains = new Map([ { chainId: 288, networkId: 288, - jsonRpcUrl: 'https://mainnet.boba.network', + jsonRpcUrl: "https://mainnet.boba.network", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.routescan.io/v2/network/mainnet/evm/288/etherscan', + specApiType: "etherscan", + specApiUrl: + "https://api.routescan.io/v2/network/mainnet/evm/288/etherscan", }, ], }, @@ -370,11 +371,11 @@ export const supportedChains = new Map([ { chainId: 2000, networkId: 2000, - jsonRpcUrl: 'https://rpc.dogechain.dog', + jsonRpcUrl: "https://rpc.dogechain.dog", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://explorer.dogechain.dog', + specApiType: "etherscan", + specApiUrl: "https://explorer.dogechain.dog", }, ], }, @@ -384,11 +385,11 @@ export const supportedChains = new Map([ { chainId: 324, networkId: 324, - jsonRpcUrl: 'https://mainnet.era.zksync.io', + jsonRpcUrl: "https://mainnet.era.zksync.io", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://block-explorer-api.mainnet.zksync.io', + specApiType: "etherscan", + specApiUrl: "https://block-explorer-api.mainnet.zksync.io", }, ], }, @@ -398,11 +399,11 @@ export const supportedChains = new Map([ { chainId: 59144, networkId: 59144, - jsonRpcUrl: 'https://rpc.linea.build', + jsonRpcUrl: "https://rpc.linea.build", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://api.lineascan.build', + specApiType: "etherscan", + specApiUrl: "https://api.lineascan.build", }, ], }, @@ -412,7 +413,7 @@ export const supportedChains = new Map([ { chainId: 204, networkId: 204, - jsonRpcUrl: 'https://opbnb.publicnode.com', + jsonRpcUrl: "https://opbnb.publicnode.com", specApiConfigs: [], }, ], @@ -421,11 +422,11 @@ export const supportedChains = new Map([ { chainId: 1088, networkId: 1088, - jsonRpcUrl: 'https://andromeda.metis.io/?owner=1088', + jsonRpcUrl: "https://andromeda.metis.io/?owner=1088", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://andromeda-explorer.metis.io', + specApiType: "etherscan", + specApiUrl: "https://andromeda-explorer.metis.io", }, ], }, @@ -435,11 +436,11 @@ export const supportedChains = new Map([ { chainId: 2222, networkId: 2222, - jsonRpcUrl: 'https://evm.kava.io', + jsonRpcUrl: "https://evm.kava.io", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://kavascan.com', + specApiType: "etherscan", + specApiUrl: "https://kavascan.com", }, ], }, @@ -449,11 +450,11 @@ export const supportedChains = new Map([ { chainId: 42170, networkId: 42170, - jsonRpcUrl: 'https://nova.arbitrum.io/rpc', + jsonRpcUrl: "https://nova.arbitrum.io/rpc", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://nova-explorer.arbitrum.io', + specApiType: "etherscan", + specApiUrl: "https://nova-explorer.arbitrum.io", }, ], }, @@ -463,11 +464,11 @@ export const supportedChains = new Map([ { chainId: 43288, networkId: 43288, - jsonRpcUrl: 'https://avax.boba.network', + jsonRpcUrl: "https://avax.boba.network", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://blockexplorer.avax.boba.network', + specApiType: "etherscan", + specApiUrl: "https://blockexplorer.avax.boba.network", }, ], }, @@ -477,11 +478,11 @@ export const supportedChains = new Map([ { chainId: 56288, networkId: 56288, - jsonRpcUrl: 'https://bnb.boba.network', + jsonRpcUrl: "https://bnb.boba.network", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://blockexplorer.bnb.boba.network', + specApiType: "etherscan", + specApiUrl: "https://blockexplorer.bnb.boba.network", }, ], }, @@ -491,11 +492,11 @@ export const supportedChains = new Map([ { chainId: 122, networkId: 122, - jsonRpcUrl: 'https://rpc.fuse.io', + jsonRpcUrl: "https://rpc.fuse.io", specApiConfigs: [ { - specApiType: 'etherscan', - specApiUrl: 'https://explorer.fuse.io', + specApiType: "etherscan", + specApiUrl: "https://explorer.fuse.io", }, ], }, @@ -505,7 +506,7 @@ export const supportedChains = new Map([ { chainId: 128, networkId: 128, - jsonRpcUrl: 'https://http-mainnet.hecochain.com', + jsonRpcUrl: "https://http-mainnet.hecochain.com", specApiConfigs: [], }, ], diff --git a/src/modules/blockscan/throttler.ts b/src/modules/blockscan/throttler.ts index a133cbe..f7504bb 100644 --- a/src/modules/blockscan/throttler.ts +++ b/src/modules/blockscan/throttler.ts @@ -1,5 +1,5 @@ /* eslint-disable no-plusplus */ -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; interface ThrottleTracker { delayMs: number; @@ -19,12 +19,12 @@ export function configureThrottle( ): void { const existingThrottle = throttleMap.get(throttleId); if (existingThrottle) { - logger.warn('Throttle already configured:', throttleId, existingThrottle); + logger.warn("Throttle already configured:", throttleId, existingThrottle); return; } logger.log( - 'Configuring throttle: (id, delayMs, limit, timeout)', + "Configuring throttle: (id, delayMs, limit, timeout)", throttleId, delayMs, limit, @@ -88,7 +88,10 @@ async function throttleDelay(throttleId: string) { * @param id: The throttle id * @param fn: The function to throttle */ -export async function throttleWrap(id: string, fn: () => Promise): Promise { +export async function throttleWrap( + id: string, + fn: () => Promise, +): Promise { const throttleTracker = throttleMap.get(id); if (!throttleTracker) { return fn(); diff --git a/src/modules/configuration/config.service.test.ts b/src/modules/configuration/config.service.test.ts index 75051db..7e8ac5d 100644 --- a/src/modules/configuration/config.service.test.ts +++ b/src/modules/configuration/config.service.test.ts @@ -1,18 +1,18 @@ -import 'reflect-metadata'; // needed when testing services -import { beforeAll, describe, expect, jest, test } from '@jest/globals'; -import { Container } from 'typedi'; +import "reflect-metadata"; // needed when testing services +import { beforeAll, describe, expect, jest, test } from "@jest/globals"; +import { Container } from "typedi"; -import { getGlobalLogger } from '@bonadocs/di'; +import { getGlobalLogger } from "@bonadocs/di"; -import { asMock, getMockLogger } from '../../test/util'; +import { asMock, getMockLogger } from "../../test/util"; -import { ConfigService, getConfigService } from './config.service'; -import ConfigNotFoundError from './error'; +import { ConfigService, getConfigService } from "./config.service"; +import ConfigNotFoundError from "./error"; -import { validateEnvVars } from './index'; +import { validateEnvVars } from "./index"; -jest.mock('typedi', () => { - const originalModule = jest.requireActual('typedi') as object; +jest.mock("typedi", () => { + const originalModule = jest.requireActual("typedi") as object; return { __esModule: true, ...originalModule, @@ -22,15 +22,15 @@ jest.mock('typedi', () => { }; }); -jest.mock('./mapping', () => { - const originalModule = jest.requireActual('./mapping') as object; +jest.mock("./mapping", () => { + const originalModule = jest.requireActual("./mapping") as object; const testConfigMapping = { jwtSecret: { - env: 'JWT_SECRET', + env: "JWT_SECRET", required: true, }, isDebugMode: { - env: 'DEBUG', + env: "DEBUG", required: false, }, }; @@ -42,97 +42,99 @@ jest.mock('./mapping', () => { }; }); -jest.mock('@bonadocs/di', () => { - const originalModule = jest.requireActual('typedi') as object; +jest.mock("@bonadocs/di", () => { + const originalModule = jest.requireActual("typedi") as object; return { __esModule: true, ...originalModule, - diConstants: { logger: 'logger' }, + diConstants: { logger: "logger" }, getGlobalLogger: jest.fn(), }; }); -describe('config.service', () => { +describe("config.service", () => { beforeAll(() => { - process.env.ENV_PREFIX = 'TEST'; + process.env.ENV_PREFIX = "TEST"; }); - describe('ConfigService', () => { - describe('get', () => { - test('should return configuration with mapped name', async () => { + describe("ConfigService", () => { + describe("get", () => { + test("should return configuration with mapped name", async () => { // arrange const mockLogger = getMockLogger(); const service = new ConfigService(mockLogger); - const testValue = 'test'; + const testValue = "test"; process.env.JWT_SECRET = testValue; // act - const value = service.get('jwtSecret'); + const value = service.get("jwtSecret"); // assert expect(value).toBe(testValue); }); - test('should return default value if a default is passed and the expected value is undefined', async () => { + test("should return default value if a default is passed and the expected value is undefined", async () => { // arrange const mockLogger = getMockLogger(); const service = new ConfigService(mockLogger); - const testValue = 'test'; + const testValue = "test"; delete process.env.JWT_SECRET; // act - const value = service.get('jwtSecret', testValue); + const value = service.get("jwtSecret", testValue); // assert expect(value).toBe(testValue); }); - test('should return null if the value does not exist', async () => { + test("should return null if the value does not exist", async () => { // arrange const mockLogger = getMockLogger(); const service = new ConfigService(mockLogger); delete process.env.JWT_SECRET; // act - const value = service.get('jwtSecret'); + const value = service.get("jwtSecret"); // assert expect(value).toBeNull(); }); }); - describe('getRequired', () => { - test('should return configuration with mapped name', async () => { + describe("getRequired", () => { + test("should return configuration with mapped name", async () => { // arrange const mockLogger = getMockLogger(); const service = new ConfigService(mockLogger); - const testValue = 'test'; + const testValue = "test"; process.env.JWT_SECRET = testValue; // act - const value = service.getRequired('jwtSecret'); + const value = service.getRequired("jwtSecret"); // assert expect(value).toBe(testValue); }); - test('should throw an error if the config is not defined', async () => { + test("should throw an error if the config is not defined", async () => { // arrange const mockLogger = getMockLogger(); const service = new ConfigService(mockLogger); delete process.env.JWT_SECRET; // act and assert - expect(() => service.getRequired('jwtSecret')).toThrow(ConfigNotFoundError); + expect(() => service.getRequired("jwtSecret")).toThrow( + ConfigNotFoundError, + ); }); }); - describe('isDebugMode', () => { - test('should return true if DEBUG is true', async () => { + describe("isDebugMode", () => { + test("should return true if DEBUG is true", async () => { // arrange const mockLogger = getMockLogger(); const service = new ConfigService(mockLogger); - process.env.DEBUG = 'true'; + process.env.DEBUG = "true"; // act const value = service.isDebugMode(); @@ -145,7 +147,7 @@ describe('config.service', () => { // arrange const mockLogger = getMockLogger(); const service = new ConfigService(mockLogger); - process.env.DEBUG = 'random content'; + process.env.DEBUG = "random content"; const isTruthy = !!process.env.DEBUG; // act @@ -156,7 +158,7 @@ describe('config.service', () => { expect(value).toBe(false); }); - test('should return false if DEBUG is not defined', async () => { + test("should return false if DEBUG is not defined", async () => { // arrange const mockLogger = getMockLogger(); const service = new ConfigService(mockLogger); @@ -171,8 +173,8 @@ describe('config.service', () => { }); }); - describe('getConfigService', () => { - test('should return a ConfigService instance from the global DI container', () => { + describe("getConfigService", () => { + test("should return a ConfigService instance from the global DI container", () => { // arrange const mockLogger = getMockLogger(); const configService = new ConfigService(mockLogger); @@ -186,22 +188,22 @@ describe('config.service', () => { }); }); - describe('validateRequiredEnvVars', () => { - test('should throw an error if a required env var is missing', async () => { + describe("validateRequiredEnvVars", () => { + test("should throw an error if a required env var is missing", async () => { // arrange delete process.env.JWT_SECRET; // act and assert expect(() => validateEnvVars()).toThrow( - 'Missing or invalid required environment variables: JWT_SECRET', + "Missing or invalid required environment variables: JWT_SECRET", ); }); - test('should log a warning if a non-required env var is missing', async () => { + test("should log a warning if a non-required env var is missing", async () => { // arrange const mockLogger = getMockLogger(); asMock(getGlobalLogger).mockReturnValue(mockLogger); - process.env.JWT_SECRET = 'test'; + process.env.JWT_SECRET = "test"; delete process.env.DEBUG; // act @@ -209,14 +211,14 @@ describe('config.service', () => { // assert expect(mockLogger.warn).toHaveBeenCalledWith( - 'Warning: Missing or invalid environment variables: DEBUG', + "Warning: Missing or invalid environment variables: DEBUG", ); }); - test('should not throw an error if all env vars are present', async () => { + test("should not throw an error if all env vars are present", async () => { // arrange - process.env.JWT_SECRET = 'test'; - process.env.DEBUG = 'test-debug'; + process.env.JWT_SECRET = "test"; + process.env.DEBUG = "test-debug"; const mockLogger = getMockLogger(); asMock(getGlobalLogger).mockReturnValue(mockLogger); diff --git a/src/modules/configuration/config.service.ts b/src/modules/configuration/config.service.ts index ab118c2..5d55b99 100644 --- a/src/modules/configuration/config.service.ts +++ b/src/modules/configuration/config.service.ts @@ -1,11 +1,16 @@ -import { Container, Inject, Service } from 'typedi'; +import { Container, Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import ConfigNotFoundError from './error'; -import { ConfigKey, configMapping, ConfigValue, isTransformConfigValue } from './mapping'; -import { getEnv } from './util'; +import ConfigNotFoundError from "./error"; +import { + ConfigKey, + configMapping, + ConfigValue, + isTransformConfigValue, +} from "./mapping"; +import { getEnv } from "./util"; /** * This service is used to access configuration values your application needs. @@ -19,7 +24,9 @@ import { getEnv } from './util'; */ @Service({ global: true }) export class ConfigService { - constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} + constructor( + @Inject(diConstants.logger) private readonly logger: BonadocsLogger, + ) {} /** * Get the value of a potentially optional configuration key @@ -41,7 +48,10 @@ export class ConfigService { return value; } - const e = new ConfigNotFoundError(`Missing required environment variable: ${key}`, key); + const e = new ConfigNotFoundError( + `Missing required environment variable: ${key}`, + key, + ); this.logger.error(e); throw e; } @@ -76,11 +86,11 @@ export class ConfigService { * Check if the application is running in debug mode */ isDebugMode(): boolean { - return this.get('isDebugMode')?.toLowerCase() === 'true'; + return this.get("isDebugMode")?.toLowerCase() === "true"; } getGlobalServiceKey(): string { - return 'disburse-global'; + return "disburse-global"; } } diff --git a/src/modules/configuration/error.ts b/src/modules/configuration/error.ts index b6b4348..315f989 100644 --- a/src/modules/configuration/error.ts +++ b/src/modules/configuration/error.ts @@ -4,6 +4,6 @@ export default class ConfigNotFoundError extends Error { readonly configName: string, ) { super(message); - this.name = 'ConfigNotFoundError'; + this.name = "ConfigNotFoundError"; } } diff --git a/src/modules/configuration/index.ts b/src/modules/configuration/index.ts index 3af8580..8df113d 100644 --- a/src/modules/configuration/index.ts +++ b/src/modules/configuration/index.ts @@ -1,13 +1,13 @@ -import { getGlobalLogger } from '@bonadocs/di'; +import { getGlobalLogger } from "@bonadocs/di"; -import { configValues, isTransformConfigValue } from './mapping'; -import { getEnv } from './util'; +import { configValues, isTransformConfigValue } from "./mapping"; +import { getEnv } from "./util"; -export { ConfigService, getConfigService } from './config.service'; -export { default as ConfigNotFoundError } from './error'; -export * from './config.interface'; +export { ConfigService, getConfigService } from "./config.service"; +export { default as ConfigNotFoundError } from "./error"; +export * from "./config.interface"; -export { removeUnknownEnvVars } from './util'; +export { removeUnknownEnvVars } from "./util"; export function validateEnvVars(): void { const missingRequiredEnvVars: string[] = []; @@ -25,7 +25,9 @@ export function validateEnvVars(): void { const isDefined = value != null; if (!isDefined) { - const list = isRequired ? missingRequiredEnvVars : missingOptionalEnvVars; + const list = isRequired + ? missingRequiredEnvVars + : missingOptionalEnvVars; list.push(variable.env); } } catch { @@ -35,12 +37,22 @@ export function validateEnvVars(): void { } if (missingRequiredEnvVars.length || invalidRequiredEnvVars.length) { - const variables = [...missingRequiredEnvVars, ...invalidRequiredEnvVars].join(', '); - throw new Error(`Missing or invalid required environment variables: ${variables}`); + const variables = [ + ...missingRequiredEnvVars, + ...invalidRequiredEnvVars, + ].join(", "); + throw new Error( + `Missing or invalid required environment variables: ${variables}`, + ); } if (missingOptionalEnvVars.length || invalidOptionalEnvVars.length) { - const variables = [...missingOptionalEnvVars, ...invalidOptionalEnvVars].join(', '); - getGlobalLogger().warn(`Warning: Missing or invalid environment variables: ${variables}`); + const variables = [ + ...missingOptionalEnvVars, + ...invalidOptionalEnvVars, + ].join(", "); + getGlobalLogger().warn( + `Warning: Missing or invalid environment variables: ${variables}`, + ); } } diff --git a/src/modules/configuration/mapping.ts b/src/modules/configuration/mapping.ts index 59e747f..45fb855 100644 --- a/src/modules/configuration/mapping.ts +++ b/src/modules/configuration/mapping.ts @@ -1,5 +1,5 @@ -import { ServiceConfiguration } from './config.interface'; -import { transformJSON, transformNumber } from './util'; +import { ServiceConfiguration } from "./config.interface"; +import { transformJSON, transformNumber } from "./util"; /** * This is a mapping of all the environment variables that the project uses. @@ -8,72 +8,78 @@ import { transformJSON, transformNumber } from './util'; */ export const configMapping = { port: { - env: 'PORT', + env: "PORT", required: true, - transform: (value: string | null | undefined) => transformNumber('PORT', value), + transform: (value: string | null | undefined) => + transformNumber("PORT", value), }, gcp: { - env: 'SERVICE_CONFIGURATION', + env: "SERVICE_CONFIGURATION", required: true, transform: (value: string | null | undefined) => - transformJSON('SERVICE_CONFIGURATION', value).gcp, + transformJSON("SERVICE_CONFIGURATION", value).gcp, }, mail: { - env: 'SERVICE_CONFIGURATION', + env: "SERVICE_CONFIGURATION", required: true, transform: (value: string | null | undefined) => - transformJSON('SERVICE_CONFIGURATION', value).mailgun, + transformJSON("SERVICE_CONFIGURATION", value) + .mailgun, }, stripe: { - env: 'SERVICE_CONFIGURATION', + env: "SERVICE_CONFIGURATION", required: false, transform: (value: string | null | undefined) => - transformJSON('SERVICE_CONFIGURATION', value).stripe, + transformJSON("SERVICE_CONFIGURATION", value) + .stripe, }, paddle: { - env: 'SERVICE_CONFIGURATION', + env: "SERVICE_CONFIGURATION", required: false, transform: (value: string | null | undefined) => - transformJSON('SERVICE_CONFIGURATION', value).paddle, + transformJSON("SERVICE_CONFIGURATION", value) + .paddle, }, jwtSecret: { - env: 'JWT_SECRET', + env: "JWT_SECRET", required: true, }, jwtTokenTtl: { - env: 'JWT_TOKEN_TTL', + env: "JWT_TOKEN_TTL", required: false, }, blockscan: { - env: 'SERVICE_CONFIGURATION', + env: "SERVICE_CONFIGURATION", required: true, transform: (value: string | null | undefined) => - transformJSON('SERVICE_CONFIGURATION', value).blockscan, + transformJSON("SERVICE_CONFIGURATION", value) + .blockscan, }, tenderly: { - env: 'SERVICE_CONFIGURATION', + env: "SERVICE_CONFIGURATION", required: true, transform: (value: string | null | undefined) => - transformJSON('SERVICE_CONFIGURATION', value).tenderly, + transformJSON("SERVICE_CONFIGURATION", value) + .tenderly, }, corsOrigins: { - env: 'CORS_ORIGINS', + env: "CORS_ORIGINS", required: false, transform: (value: string | null | undefined) => { if (!value) { - return '*'; + return "*"; } - return value.split(',').map((origin) => origin.trim()); + return value.split(",").map((origin) => origin.trim()); }, }, @@ -86,7 +92,7 @@ export const configMapping = { // for debug mode detection: optional isDebugMode: { - env: 'DEBUG', + env: "DEBUG", required: false, }, } as const; @@ -103,5 +109,5 @@ export const configValues = Object.values(configMapping) as ConfigValue[]; export function isTransformConfigValue( value: ConfigValue, ): value is Required> { - return 'transform' in value && typeof value.transform === 'function'; + return "transform" in value && typeof value.transform === "function"; } diff --git a/src/modules/configuration/util.test.ts b/src/modules/configuration/util.test.ts index 6204474..36c00d3 100644 --- a/src/modules/configuration/util.test.ts +++ b/src/modules/configuration/util.test.ts @@ -1,54 +1,60 @@ -import process from 'node:process'; +import process from "node:process"; -import { beforeEach, describe, expect, jest, test } from '@jest/globals'; +import { beforeEach, describe, expect, jest, test } from "@jest/globals"; -import { asMock } from '../../test/util'; -import { lazy } from '../shared'; +import { asMock } from "../../test/util"; +import { lazy } from "../shared"; -import { envPrefix, getEnv, removeUnknownEnvVars, setDefaultEnvVar, transformNumber } from './util'; +import { + envPrefix, + getEnv, + removeUnknownEnvVars, + setDefaultEnvVar, + transformNumber, +} from "./util"; -jest.mock('../shared', () => ({ - ...(jest.requireActual('../shared')), +jest.mock("../shared", () => ({ + ...(jest.requireActual("../shared")), __esModule: true, lazy: jest.fn((fn) => fn), })); -describe('Configuration Util', () => { +describe("Configuration Util", () => { beforeEach(() => { asMock(lazy).mockImplementation((fn) => fn); }); - describe('getEnv', () => { - test('should return the value of the environment variable', () => { + describe("getEnv", () => { + test("should return the value of the environment variable", () => { // arrange - process.env.KEY = 'value'; + process.env.KEY = "value"; // act - const result = getEnv('KEY'); + const result = getEnv("KEY"); // assert - expect(result).toBe('value'); + expect(result).toBe("value"); }); }); - describe('envPrefix', () => { - test('should return the value of ENV_PREFIX', () => { + describe("envPrefix", () => { + test("should return the value of ENV_PREFIX", () => { // arrange - process.env.ENV_PREFIX = 'TEST'; + process.env.ENV_PREFIX = "TEST"; // act const value = envPrefix(); // assert - expect(value).toBe('TEST'); + expect(value).toBe("TEST"); }); }); - describe('removeUnknownEnvVars', () => { - test('should remove env keys that do not start with the prefix', () => { + describe("removeUnknownEnvVars", () => { + test("should remove env keys that do not start with the prefix", () => { // arrange - process.env.ENV_PREFIX = 'TEST'; - process.env.TEST_KEY1 = 'value1'; - process.env.KEY2 = 'value2'; + process.env.ENV_PREFIX = "TEST"; + process.env.TEST_KEY1 = "value1"; + process.env.KEY2 = "value2"; // act removeUnknownEnvVars(new Set()); @@ -57,51 +63,51 @@ describe('Configuration Util', () => { expect(process.env.KEY2).toBeUndefined(); }); - test('should not remove env keys that are passed in the exclude set', () => { + test("should not remove env keys that are passed in the exclude set", () => { // arrange - process.env.ENV_PREFIX = 'TEST'; - process.env.TEST_KEY1 = 'value1'; - process.env.KEY2 = 'value2'; + process.env.ENV_PREFIX = "TEST"; + process.env.TEST_KEY1 = "value1"; + process.env.KEY2 = "value2"; // act - removeUnknownEnvVars(new Set(['KEY2'])); + removeUnknownEnvVars(new Set(["KEY2"])); // assert - expect(process.env.KEY2).toBe('value2'); + expect(process.env.KEY2).toBe("value2"); }); }); - describe('setDefaultEnvVar', () => { - test('should set the environment variable if not already set', () => { + describe("setDefaultEnvVar", () => { + test("should set the environment variable if not already set", () => { // arrange - process.env.ENV_PREFIX = 'TEST'; + process.env.ENV_PREFIX = "TEST"; delete process.env.TEST_KEY; // act - setDefaultEnvVar('KEY', 'value'); + setDefaultEnvVar("KEY", "value"); // assert - expect(process.env.TEST_KEY).toBe('value'); + expect(process.env.TEST_KEY).toBe("value"); }); - test('should not overwrite an existing environment variable', () => { + test("should not overwrite an existing environment variable", () => { // arrange - process.env.ENV_PREFIX = 'TEST'; - process.env.TEST_KEY = 'existing_value'; + process.env.ENV_PREFIX = "TEST"; + process.env.TEST_KEY = "existing_value"; // act - setDefaultEnvVar('KEY', 'new_value'); + setDefaultEnvVar("KEY", "new_value"); // assert - expect(process.env.TEST_KEY).toBe('existing_value'); + expect(process.env.TEST_KEY).toBe("existing_value"); }); }); - describe('transformNumber', () => { - test('should transform valid string to number', () => { + describe("transformNumber", () => { + test("should transform valid string to number", () => { // arrange - const envKey = 'NIBSS_TIMEOUT'; - const value = '3000'; + const envKey = "NIBSS_TIMEOUT"; + const value = "3000"; // act const result = transformNumber(envKey, value); @@ -110,22 +116,28 @@ describe('Configuration Util', () => { expect(result).toBe(3000); }); - test('should throw an error if value is null or undefined', () => { + test("should throw an error if value is null or undefined", () => { // arrange - const envKey = 'NIBSS_TIMEOUT'; + const envKey = "NIBSS_TIMEOUT"; // act & assert - expect(() => transformNumber(envKey, null)).toThrow(`${envKey} is not set`); - expect(() => transformNumber(envKey, undefined)).toThrow(`${envKey} is not set`); + expect(() => transformNumber(envKey, null)).toThrow( + `${envKey} is not set`, + ); + expect(() => transformNumber(envKey, undefined)).toThrow( + `${envKey} is not set`, + ); }); - test('should throw an error if value is not a number', () => { + test("should throw an error if value is not a number", () => { // arrange - const envKey = 'NIBSS_TIMEOUT'; - const value = 'invalid-number'; + const envKey = "NIBSS_TIMEOUT"; + const value = "invalid-number"; // act & assert - expect(() => transformNumber(envKey, value)).toThrow(`${envKey} is not a number`); + expect(() => transformNumber(envKey, value)).toThrow( + `${envKey} is not a number`, + ); }); }); }); diff --git a/src/modules/configuration/util.ts b/src/modules/configuration/util.ts index 1de1cc3..fd846e7 100644 --- a/src/modules/configuration/util.ts +++ b/src/modules/configuration/util.ts @@ -1,4 +1,4 @@ -import { lazy } from '../shared'; +import { lazy } from "../shared"; const exclusions: Set = new Set(); @@ -17,7 +17,10 @@ export function getEnv(key: string): string | undefined { * @param envKey * @param value */ -export function transformNumber(envKey: string, value: string | null | undefined): number { +export function transformNumber( + envKey: string, + value: string | null | undefined, +): number { if (!value) { throw new Error(`${envKey} is not set`); } @@ -38,7 +41,10 @@ export function transformNumber(envKey: string, value: string | null | undefined * @returns T */ -export function transformJSON(envKey: string, value: string | null | undefined): T { +export function transformJSON( + envKey: string, + value: string | null | undefined, +): T { if (!value) { throw new Error(`${envKey} is not set`); } @@ -59,9 +65,9 @@ export function transformJSON(envKey: string, value: string | null | undefine * variables do not clash across pods in a k8s cluster. */ export const envPrefix = lazy(() => { - const value = process.env.ENV_PREFIX || 'ENV'; + const value = process.env.ENV_PREFIX || "ENV"; if (!value) { - throw new Error('ENV_PREFIX is not set'); + throw new Error("ENV_PREFIX is not set"); } return value; }); diff --git a/src/modules/connection/dbcontext.ts b/src/modules/connection/dbcontext.ts index ac72022..f1922a3 100644 --- a/src/modules/connection/dbcontext.ts +++ b/src/modules/connection/dbcontext.ts @@ -1,13 +1,13 @@ /* eslint-disable no-await-in-loop */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import type { QueryConfig, QueryResult } from 'pg'; +import type { QueryConfig, QueryResult } from "pg"; -import { BonadocsLogger, createLogger } from '@bonadocs/logger'; +import { BonadocsLogger, createLogger } from "@bonadocs/logger"; -import { getPool } from '.'; +import { getPool } from "."; -const dbLogger: BonadocsLogger = createLogger({ context: 'db-connection' }); +const dbLogger: BonadocsLogger = createLogger({ context: "db-connection" }); export class DbValidationError extends Error { constructor( @@ -16,7 +16,7 @@ export class DbValidationError extends Error { readonly result: QueryResult, ) { super(message); - this.name = 'DbValidationError'; + this.name = "DbValidationError"; } } @@ -44,10 +44,15 @@ export function withDbContext Promise>( ): TypedPropertyDescriptor { const originalMethod = descriptor.value!; - descriptor.value = async function wrappedFunction(this: object, ...args: any[]) { + descriptor.value = async function wrappedFunction( + this: object, + ...args: any[] + ) { const lastArg = args[args.length - 1] as DbContext | undefined | null; const dbContext = - args.length > 0 && lastArg?.isDbContext === true ? (lastArg as DbContext) : undefined; + args.length > 0 && lastArg?.isDbContext === true + ? (lastArg as DbContext) + : undefined; // If the last argument is a DbContext, we don't need to create a new one if (dbContext) { @@ -62,9 +67,9 @@ export function withDbContext Promise>( entrypoint: originalMethod.name, query: async (config) => { const result = await poolClient.query(config); - if ('validateResult' in config && !config.validateResult(result)) { + if ("validateResult" in config && !config.validateResult(result)) { throw new DbValidationError( - config.validationErrorMessage || 'Query validation failed.', + config.validationErrorMessage || "Query validation failed.", config, result, ); @@ -73,19 +78,24 @@ export function withDbContext Promise>( }, async beginTransaction() { - const query = transactionCount === 0 ? 'BEGIN' : `SAVEPOINT sp_${transactionCount}`; + const query = + transactionCount === 0 ? "BEGIN" : `SAVEPOINT sp_${transactionCount}`; await poolClient.query(query); transactionCount += 1; }, async commitTransaction() { const commitQuery = - transactionCount === 1 ? 'COMMIT' : `RELEASE SAVEPOINT sp_${transactionCount - 1}`; + transactionCount === 1 + ? "COMMIT" + : `RELEASE SAVEPOINT sp_${transactionCount - 1}`; await poolClient.query(commitQuery); transactionCount -= 1; }, async rollbackTransaction() { const rollBackQuery = - transactionCount === 1 ? 'ROLLBACK' : `ROLLBACK TO SAVEPOINT sp_${transactionCount - 1}`; + transactionCount === 1 + ? "ROLLBACK" + : `ROLLBACK TO SAVEPOINT sp_${transactionCount - 1}`; await poolClient.query(rollBackQuery); transactionCount -= 1; }, diff --git a/src/modules/connection/index.ts b/src/modules/connection/index.ts index 610b433..a754707 100644 --- a/src/modules/connection/index.ts +++ b/src/modules/connection/index.ts @@ -1,4 +1,4 @@ -import pg from 'pg'; +import pg from "pg"; let pool: pg.Pool | undefined; diff --git a/src/modules/contract/contract.controller.ts b/src/modules/contract/contract.controller.ts index 9997851..7f633cd 100644 --- a/src/modules/contract/contract.controller.ts +++ b/src/modules/contract/contract.controller.ts @@ -1,32 +1,35 @@ -import { request } from 'express'; -import { Get, JsonController, QueryParam } from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +import { request } from "express"; +import { Get, JsonController, QueryParam } from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { JsonResponse } from '../shared'; +import { JsonResponse } from "../shared"; -import { ContractABIRequest, ContractABIResponse } from './contract.interface'; -import { ContractService } from './contract.service'; +import { ContractABIRequest, ContractABIResponse } from "./contract.interface"; +import { ContractService } from "./contract.service"; @Service() -@JsonController('/contracts') +@JsonController("/contracts") export class ContractController { constructor(@Inject() private readonly contractService: ContractService) {} - @Get('') + @Get("") async get( - @QueryParam('chainId') chainId: number, - @QueryParam('address') address: string, + @QueryParam("chainId") chainId: number, + @QueryParam("address") address: string, ): Promise> { const requestQuery: ContractABIRequest = { chainId, address, }; - const response = await this.contractService.getContractHash(request.auth, requestQuery); + const response = await this.contractService.getContractHash( + request.auth, + requestQuery, + ); return { data: response, - status: 'successful', - message: 'Contract ABI fetched successfully', + status: "successful", + message: "Contract ABI fetched successfully", }; } } diff --git a/src/modules/contract/contract.service.test.ts b/src/modules/contract/contract.service.test.ts index 1f2e3f7..65d9f5a 100644 --- a/src/modules/contract/contract.service.test.ts +++ b/src/modules/contract/contract.service.test.ts @@ -1,16 +1,16 @@ -import { beforeEach, describe, expect, it, jest } from '@jest/globals'; -import 'reflect-metadata'; +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; +import "reflect-metadata"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { generateMockObject, getMockLogger } from '../../test/util'; -import { ConfigService } from '../configuration/config.service'; -import { EvmContractRepository } from '../repositories/evm-contracts/evm-contracts.repository'; -import { Storage } from '../storage'; +import { generateMockObject, getMockLogger } from "../../test/util"; +import { ConfigService } from "../configuration/config.service"; +import { EvmContractRepository } from "../repositories/evm-contracts/evm-contracts.repository"; +import { Storage } from "../storage"; -import { ContractService } from './contract.service'; +import { ContractService } from "./contract.service"; -describe('ContractService', () => { +describe("ContractService", () => { let logger: jest.Mocked; let contractRepository: jest.Mocked; let configService: jest.Mocked; @@ -19,30 +19,38 @@ describe('ContractService', () => { beforeEach(() => { logger = getMockLogger(); - contractRepository = generateMockObject('getContractAbiHash', 'createContract'); - configService = generateMockObject('getTransformed'); - storage = generateMockObject('downloadFile', 'uploadFile'); - contactService = new ContractService(logger, contractRepository, configService, storage); + contractRepository = generateMockObject( + "getContractAbiHash", + "createContract", + ); + configService = generateMockObject("getTransformed"); + storage = generateMockObject("downloadFile", "uploadFile"); + contactService = new ContractService( + logger, + contractRepository, + configService, + storage, + ); }); - describe('get contract hash', () => { - it('get contract hash should be successful if all is well', async () => { - contractRepository.getContractAbiHash.mockResolvedValue('test'); + describe("get contract hash", () => { + it("get contract hash should be successful if all is well", async () => { + contractRepository.getContractAbiHash.mockResolvedValue("test"); contractRepository.createContract.mockResolvedValue(true); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } return null as T; }); storage.uploadFile.mockResolvedValue(); - storage.downloadFile.mockResolvedValue('test'); + storage.downloadFile.mockResolvedValue("test"); // act const result = await contactService.getContractHash( @@ -51,14 +59,14 @@ describe('ContractService', () => { userId: 1, }, { - address: 'test-address', + address: "test-address", chainId: 1, }, ); // assert expect(result).toBeDefined(); - expect(result.address).toBe('test-address'); + expect(result.address).toBe("test-address"); expect(result.chainId).toBe(1); }); }); diff --git a/src/modules/contract/contract.service.ts b/src/modules/contract/contract.service.ts index 2c0d48e..bd4d703 100644 --- a/src/modules/contract/contract.service.ts +++ b/src/modules/contract/contract.service.ts @@ -1,18 +1,18 @@ -import { createHash } from 'crypto'; +import { createHash } from "crypto"; -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { getVerifiedContractABI } from '../blockscan/index'; -import { GCP } from '../configuration/config.interface'; -import { ConfigService } from '../configuration/config.service'; -import { ApplicationError, applicationErrorCodes } from '../errors'; -import { EvmContractRepository } from '../repositories/evm-contracts/evm-contracts.repository'; -import { Storage } from '../storage'; +import { getVerifiedContractABI } from "../blockscan/index"; +import { GCP } from "../configuration/config.interface"; +import { ConfigService } from "../configuration/config.service"; +import { ApplicationError, applicationErrorCodes } from "../errors"; +import { EvmContractRepository } from "../repositories/evm-contracts/evm-contracts.repository"; +import { Storage } from "../storage"; -import { ContractABIRequest, ContractABIResponse } from './contract.interface'; +import { ContractABIRequest, ContractABIResponse } from "./contract.interface"; @Service() export class ContractService { @@ -32,9 +32,12 @@ export class ContractService { request.chainId, request.address, ); - const gcpConfig = this.configService.getTransformed('gcp'); + const gcpConfig = this.configService.getTransformed("gcp"); if (abiHash) { - const abi = await this.storage.downloadFile(gcpConfig.abisBucket, `${abiHash}.json`); + const abi = await this.storage.downloadFile( + gcpConfig.abisBucket, + `${abiHash}.json`, + ); if (abi) { return { @@ -44,7 +47,7 @@ export class ContractService { }; } } - this.logger.info('Contract not loaded. Loading from API.'); + this.logger.info("Contract not loaded. Loading from API."); const verifiedSpec = await getVerifiedContractABI( this.logger, request.chainId, @@ -52,17 +55,21 @@ export class ContractService { ); if (!verifiedSpec) { throw new ApplicationError({ - message: 'Contract not found', + message: "Contract not found", logger: this.logger, errorCode: applicationErrorCodes.notFound, }); } - const hash = createHash('sha256').update(verifiedSpec).digest('hex'); - this.logger.info('Loaded ABI from API. Caching...'); + const hash = createHash("sha256").update(verifiedSpec).digest("hex"); + this.logger.info("Loaded ABI from API. Caching..."); try { - await this.storage.uploadFile(gcpConfig.abisBucket, `${hash}.json`, verifiedSpec); + await this.storage.uploadFile( + gcpConfig.abisBucket, + `${hash}.json`, + verifiedSpec, + ); } catch (e) { - this.logger.error('Error caching ABI', e); + this.logger.error("Error caching ABI", e); } try { @@ -72,7 +79,7 @@ export class ContractService { abiHash: hash, }); } catch (e) { - this.logger.error('Error storing contract in DB', e); + this.logger.error("Error storing contract in DB", e); } return { diff --git a/src/modules/contract/index.ts b/src/modules/contract/index.ts index ad823ff..192e28b 100644 --- a/src/modules/contract/index.ts +++ b/src/modules/contract/index.ts @@ -1 +1 @@ -export { ContractController } from './contract.controller'; +export { ContractController } from "./contract.controller"; diff --git a/src/modules/coupon/coupon.controller.ts b/src/modules/coupon/coupon.controller.ts index a01e3e6..a582330 100644 --- a/src/modules/coupon/coupon.controller.ts +++ b/src/modules/coupon/coupon.controller.ts @@ -1,30 +1,39 @@ -import { Body, Get, JsonController, Param, Patch, Post } from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +import { + Body, + Get, + JsonController, + Param, + Patch, + Post, +} from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { JsonResponse } from '../shared'; +import { JsonResponse } from "../shared"; -import { CouponResponse } from './coupon.interface'; -import { CouponService } from './coupon.service'; -import { CreateCouponDto, UpdateCouponStatusDto } from './dto'; +import { CouponResponse } from "./coupon.interface"; +import { CouponService } from "./coupon.service"; +import { CreateCouponDto, UpdateCouponStatusDto } from "./dto"; @Service() -@JsonController('/coupons') +@JsonController("/coupons") export class CouponController { constructor(@Inject() private readonly couponService: CouponService) {} - @Post('/') - async create(@Body({ validate: true }) payload: CreateCouponDto): Promise> { + @Post("/") + async create( + @Body({ validate: true }) payload: CreateCouponDto, + ): Promise> { const response = await this.couponService.create(payload); return { data: response, - status: 'successful', - message: 'Created Coupon successfully', + status: "successful", + message: "Created Coupon successfully", }; } - @Patch('/:couponId') + @Patch("/:couponId") async change_status( - @Param('couponId') couponId: number, + @Param("couponId") couponId: number, @Body({ validate: true }) payload: UpdateCouponStatusDto, ): Promise> { const response = await this.couponService.changeStatus({ @@ -33,38 +42,44 @@ export class CouponController { }); return { data: response, - status: 'successful', - message: 'Updated Coupon successfully', + status: "successful", + message: "Updated Coupon successfully", }; } - @Get('/get-by-projectId/:projectId') - async list(@Param('projectId') projectId: number): Promise> { + @Get("/get-by-projectId/:projectId") + async list( + @Param("projectId") projectId: number, + ): Promise> { const response = await this.couponService.listCouponByProjectId(projectId); return { data: response, - status: 'successful', - message: 'Coupon Get Successfully', + status: "successful", + message: "Coupon Get Successfully", }; } - @Get('/:couponId') - async get(@Param('couponId') couponId: number): Promise> { + @Get("/:couponId") + async get( + @Param("couponId") couponId: number, + ): Promise> { const response = await this.couponService.getById(couponId); return { data: response, - status: 'successful', - message: 'Coupon Get Successfully', + status: "successful", + message: "Coupon Get Successfully", }; } - @Get('/get-by-code/:code') - async getByCode(@Param('code') code: string): Promise> { + @Get("/get-by-code/:code") + async getByCode( + @Param("code") code: string, + ): Promise> { const response = await this.couponService.getByCode(code); return { data: response, - status: 'successful', - message: 'Coupon Get Successfully', + status: "successful", + message: "Coupon Get Successfully", }; } } diff --git a/src/modules/coupon/coupon.interface.ts b/src/modules/coupon/coupon.interface.ts index 5e9c0b9..cb8d2d6 100644 --- a/src/modules/coupon/coupon.interface.ts +++ b/src/modules/coupon/coupon.interface.ts @@ -1,4 +1,4 @@ -import { SubscriptionStatus } from '../shared/types/subscriptions'; +import { SubscriptionStatus } from "../shared/types/subscriptions"; export interface CreateCouponRequest { project_id: number; diff --git a/src/modules/coupon/coupon.service.ts b/src/modules/coupon/coupon.service.ts index 4657516..d137a9e 100644 --- a/src/modules/coupon/coupon.service.ts +++ b/src/modules/coupon/coupon.service.ts @@ -1,14 +1,18 @@ -import { Inject, Service } from 'typedi'; -import { v4 as uuidv4 } from 'uuid'; +import { Inject, Service } from "typedi"; +import { v4 as uuidv4 } from "uuid"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ApplicationError, applicationErrorCodes } from '../errors'; -import { CouponDto, CouponRepository, CreateCouponDto } from '../repositories/coupon'; -import { SubscriptionStatus } from '../shared/types/subscriptions'; +import { ApplicationError, applicationErrorCodes } from "../errors"; +import { + CouponDto, + CouponRepository, + CreateCouponDto, +} from "../repositories/coupon"; +import { SubscriptionStatus } from "../shared/types/subscriptions"; -import { CreateCouponRequest, UpdateCouponStatus } from './coupon.interface'; +import { CreateCouponRequest, UpdateCouponStatus } from "./coupon.interface"; @Service() export class CouponService { @@ -19,31 +23,35 @@ export class CouponService { async create(request: CreateCouponRequest): Promise { const coupon: CreateCouponDto = { - code: this.generateUUIDCouponCode('bonadocs'), + code: this.generateUUIDCouponCode("bonadocs"), date_expire: request.date_expire, plan_id: request.plan_id, project_id: request.project_id, status: SubscriptionStatus.Active, }; // validate if any active coupon exists for the project on the selected plan - const existingCoupons = await this.couponRepository.getCouponsByProjectIdAndPlanId( - request.project_id, - request.plan_id, - ); + const existingCoupons = + await this.couponRepository.getCouponsByProjectIdAndPlanId( + request.project_id, + request.plan_id, + ); if (existingCoupons !== undefined) { - this.logger.error('Active coupon already exists for the project on the selected plan'); + this.logger.error( + "Active coupon already exists for the project on the selected plan", + ); throw new ApplicationError({ logger: this.logger, - message: 'Active coupon already exists for the project on the selected plan', + message: + "Active coupon already exists for the project on the selected plan", errorCode: applicationErrorCodes.invalidRequest, }); } const couponCode = await this.couponRepository.createCoupon(coupon); if (couponCode === undefined) { - this.logger.error('Error creating coupon'); + this.logger.error("Error creating coupon"); throw new ApplicationError({ logger: this.logger, - message: 'Unable to create coupon', + message: "Unable to create coupon", errorCode: applicationErrorCodes.invalidRequest, }); } @@ -51,12 +59,15 @@ export class CouponService { } async changeStatus(request: UpdateCouponStatus): Promise { - const code = await this.couponRepository.changeCouponStatus(request.id, request.status); + const code = await this.couponRepository.changeCouponStatus( + request.id, + request.status, + ); if (code === undefined) { - this.logger.error('Error updating coupon status'); + this.logger.error("Error updating coupon status"); throw new ApplicationError({ logger: this.logger, - message: 'Unable to update coupon status', + message: "Unable to update coupon status", errorCode: applicationErrorCodes.invalidRequest, }); } @@ -66,10 +77,10 @@ export class CouponService { async getById(couponId: number): Promise { const coupon = await this.couponRepository.getCouponById(couponId); if (coupon === undefined) { - this.logger.error('Error getting coupon by id'); + this.logger.error("Error getting coupon by id"); throw new ApplicationError({ logger: this.logger, - message: 'Unable to get coupon by id', + message: "Unable to get coupon by id", errorCode: applicationErrorCodes.invalidRequest, }); } @@ -79,10 +90,10 @@ export class CouponService { async getByCode(couponCode: string): Promise { const coupon = await this.couponRepository.getCouponByCode(couponCode); if (coupon === undefined) { - this.logger.error('Error getting coupon by code'); + this.logger.error("Error getting coupon by code"); throw new ApplicationError({ logger: this.logger, - message: 'Unable to get coupon by code', + message: "Unable to get coupon by code", errorCode: applicationErrorCodes.invalidRequest, }); } @@ -93,8 +104,8 @@ export class CouponService { return this.couponRepository.listCouponsByProjectId(projectId); } - private generateUUIDCouponCode(prefix = ''): string { - const code = uuidv4().split('-')[0].toUpperCase(); + private generateUUIDCouponCode(prefix = ""): string { + const code = uuidv4().split("-")[0].toUpperCase(); return prefix ? `${prefix}-${code}` : code; } } diff --git a/src/modules/coupon/dto/index.ts b/src/modules/coupon/dto/index.ts index 4f0301c..e56e084 100644 --- a/src/modules/coupon/dto/index.ts +++ b/src/modules/coupon/dto/index.ts @@ -1,2 +1,2 @@ -export { CreateCouponDto } from './create-coupon.dto'; -export { UpdateCouponStatusDto } from './update-coupon-status.dto'; +export { CreateCouponDto } from "./create-coupon.dto"; +export { UpdateCouponStatusDto } from "./update-coupon-status.dto"; diff --git a/src/modules/coupon/dto/update-coupon-status.dto.ts b/src/modules/coupon/dto/update-coupon-status.dto.ts index f5ca6eb..87dc2ec 100644 --- a/src/modules/coupon/dto/update-coupon-status.dto.ts +++ b/src/modules/coupon/dto/update-coupon-status.dto.ts @@ -1,4 +1,4 @@ -import { SubscriptionStatus } from '../../shared/types/subscriptions'; +import { SubscriptionStatus } from "../../shared/types/subscriptions"; export class UpdateCouponStatusDto { status: SubscriptionStatus; diff --git a/src/modules/coupon/index.ts b/src/modules/coupon/index.ts index be73b48..c85e1fd 100644 --- a/src/modules/coupon/index.ts +++ b/src/modules/coupon/index.ts @@ -1 +1 @@ -export { CouponController } from './coupon.controller'; +export { CouponController } from "./coupon.controller"; diff --git a/src/modules/cron/billing.cron.ts b/src/modules/cron/billing.cron.ts index 3e52d13..fd22e84 100644 --- a/src/modules/cron/billing.cron.ts +++ b/src/modules/cron/billing.cron.ts @@ -1,49 +1,51 @@ -import cron from 'node-cron'; -import { Container } from 'typedi'; +import cron from "node-cron"; +import { Container } from "typedi"; -import { getGlobalLogger } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { getGlobalLogger } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { BillingService } from '../billing/billing.service'; -import { PaddleHttpClient } from '../http/paddle-http-client'; -import { BillingRepository } from '../repositories/billing'; -import { InvoicePaymentStatus } from '../shared/types/invoice'; +import { BillingService } from "../billing/billing.service"; +import { PaddleHttpClient } from "../http/paddle-http-client"; +import { BillingRepository } from "../repositories/billing"; +import { InvoicePaymentStatus } from "../shared/types/invoice"; // cron to check due payments and send reminders export function MonitorPaymentStatus(): Promise { return new Promise((resolve, reject) => { - cron.schedule('*/15 * * * *', async () => { + cron.schedule("*/15 * * * *", async () => { const billingRepository = Container.get(BillingRepository); const paddleHttpClient = Container.get(PaddleHttpClient); const billingService = Container.get(BillingService); const logger: BonadocsLogger = getGlobalLogger(); - logger.info('Started Monitor Payment Cron Job'); + logger.info("Started Monitor Payment Cron Job"); try { - const pendingInvoicePayments = await billingRepository.getInvoicePaymentsByStatus( - InvoicePaymentStatus.PENDING, - ); + const pendingInvoicePayments = + await billingRepository.getInvoicePaymentsByStatus( + InvoicePaymentStatus.PENDING, + ); if (!pendingInvoicePayments.length) { - logger.info('No pending invoice payments found. Skipping...'); + logger.info("No pending invoice payments found. Skipping..."); return resolve(); } const transactionIds = pendingInvoicePayments .map((x) => x.metadata.transactionId) .filter(Boolean) - .join(','); + .join(","); if (!transactionIds) { - logger.warn('No valid transaction IDs found in pending payments.'); + logger.warn("No valid transaction IDs found in pending payments."); return resolve(); } - const transactions = await paddleHttpClient.getTransactions(transactionIds); + const transactions = + await paddleHttpClient.getTransactions(transactionIds); const paidTransactions = transactions.filter( - (t) => t.status === 'paid' || t.status === 'completed', + (t) => t.status === "paid" || t.status === "completed", ); if (paidTransactions.length) { @@ -51,11 +53,16 @@ export function MonitorPaymentStatus(): Promise { } const failedTransactionIds = transactions - .filter((t) => t.status === 'failed' && t.custom_data?.invoice_payment_id) + .filter( + (t) => t.status === "failed" && t.custom_data?.invoice_payment_id, + ) .map((t) => Number(t.custom_data.invoice_payment_id)); if (failedTransactionIds.length) { - await billingRepository.updateInvoiceStatusBulk(failedTransactionIds, 'failed'); + await billingRepository.updateInvoiceStatusBulk( + failedTransactionIds, + "failed", + ); } logger.info( @@ -64,7 +71,7 @@ export function MonitorPaymentStatus(): Promise { resolve(); } catch (error) { - logger.error('An error occurred in Monitor Payment Cron Job', error); + logger.error("An error occurred in Monitor Payment Cron Job", error); reject(error); } @@ -75,12 +82,13 @@ export function MonitorPaymentStatus(): Promise { export function SubscriptionRenewalSetup(): Promise { return new Promise((resolve, reject) => { - cron.schedule('0 0 * * *', async () => { + cron.schedule("0 0 * * *", async () => { const billingRepository = Container.get(BillingRepository); const billingService = Container.get(BillingService); const logger = getGlobalLogger(); try { - const subscriptions = await billingRepository.getAlmostDueSubscriptions(); + const subscriptions = + await billingRepository.getAlmostDueSubscriptions(); // select or subscriptions that have the nextSubscriptionId set const subscriptionsToProcess = subscriptions.filter( @@ -104,12 +112,14 @@ export function SubscriptionRenewalSetup(): Promise { previousSubscriptionId: x.id, }, reference, - 'Paddle', + "Paddle", reference, ); if (subscription === undefined) { - logger.error(`Unable to create next subscription for subscription with id ${x.id}`); + logger.error( + `Unable to create next subscription for subscription with id ${x.id}`, + ); return; } @@ -122,9 +132,14 @@ export function SubscriptionRenewalSetup(): Promise { }); await Promise.all(promises); resolve(); - logger.info(`Subscription renewal setup executed for ${subscriptionsToProcess.length}`); + logger.info( + `Subscription renewal setup executed for ${subscriptionsToProcess.length}`, + ); } catch (error) { - logger.error('Error occurred while running subscription renewal setup', error); + logger.error( + "Error occurred while running subscription renewal setup", + error, + ); reject(error); } }); @@ -133,16 +148,19 @@ export function SubscriptionRenewalSetup(): Promise { export function SubscriptionExpirationCheck(): Promise { return new Promise((resolve, reject) => { - cron.schedule('0 * * * *', async () => { + cron.schedule("0 * * * *", async () => { const billingRepository = Container.get(BillingRepository); const billingService = Container.get(BillingService); const logger = getGlobalLogger(); try { - const subscriptionsToProcess = await billingRepository.getDueSubscriptions(); + const subscriptionsToProcess = + await billingRepository.getDueSubscriptions(); const promises = subscriptionsToProcess.map(async (x) => { await billingService.handlerOverageCharge(x.projectId); - logger.info(`Handled Overage Implementation for due subscription with id ${x.id}`); + logger.info( + `Handled Overage Implementation for due subscription with id ${x.id}`, + ); }); await Promise.all(promises); logger.info( @@ -150,7 +168,10 @@ export function SubscriptionExpirationCheck(): Promise { ); resolve(); } catch (error) { - logger.error('Error occurred while running subscription expiration check', error); + logger.error( + "Error occurred while running subscription expiration check", + error, + ); reject(); } }); diff --git a/src/modules/cron/index.ts b/src/modules/cron/index.ts index 97dac26..9e84ce0 100644 --- a/src/modules/cron/index.ts +++ b/src/modules/cron/index.ts @@ -1 +1 @@ -export * from './billing.cron'; +export * from "./billing.cron"; diff --git a/src/modules/errors/ApplicationError.ts b/src/modules/errors/ApplicationError.ts index f0438e7..afcec17 100644 --- a/src/modules/errors/ApplicationError.ts +++ b/src/modules/errors/ApplicationError.ts @@ -1,6 +1,6 @@ -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ResponseDefinition } from '../shared/types/responseDefinition'; +import { ResponseDefinition } from "../shared/types/responseDefinition"; export class ApplicationError { errorCode?: string; @@ -32,13 +32,13 @@ export class ApplicationError { } export const applicationErrorCodes = { - unauthenticated: 'UNAUTHENTICATED', - unauthorized: 'UNAUTHORIZED', - notFound: 'NOT_FOUND', - invalidRequest: 'INVALID_REQUEST', - invalidEVMAddress: 'INVALID_EVM_ADDRESS', - invalidTransactionData: 'INVALID_TX_DATA', - simulationFailed: 'SIMULATION_FAILED', - unsupportedChain: 'UNSUPPORTED_CHAIN', - incompleteUserRegistration: 'INCOMPLETE_USER_REGISTRATION', + unauthenticated: "UNAUTHENTICATED", + unauthorized: "UNAUTHORIZED", + notFound: "NOT_FOUND", + invalidRequest: "INVALID_REQUEST", + invalidEVMAddress: "INVALID_EVM_ADDRESS", + invalidTransactionData: "INVALID_TX_DATA", + simulationFailed: "SIMULATION_FAILED", + unsupportedChain: "UNSUPPORTED_CHAIN", + incompleteUserRegistration: "INCOMPLETE_USER_REGISTRATION", }; diff --git a/src/modules/errors/InvalidRequestError.ts b/src/modules/errors/InvalidRequestError.ts index b5fc703..d6e5ad5 100644 --- a/src/modules/errors/InvalidRequestError.ts +++ b/src/modules/errors/InvalidRequestError.ts @@ -1,12 +1,12 @@ -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ApplicationError, applicationErrorCodes } from './ApplicationError'; +import { ApplicationError, applicationErrorCodes } from "./ApplicationError"; export class InvalidRequestError extends ApplicationError { constructor( logger: BonadocsLogger, userFriendlyMessage?: string, - message = 'Invalid request received', + message = "Invalid request received", ) { super({ logger, diff --git a/src/modules/errors/UnauthenticatedError.ts b/src/modules/errors/UnauthenticatedError.ts index 17e9cb6..4d7f022 100644 --- a/src/modules/errors/UnauthenticatedError.ts +++ b/src/modules/errors/UnauthenticatedError.ts @@ -1,14 +1,14 @@ -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ApplicationError, applicationErrorCodes } from './ApplicationError'; +import { ApplicationError, applicationErrorCodes } from "./ApplicationError"; export class UnauthenticatedError extends ApplicationError { constructor(logger: BonadocsLogger, message?: string) { super({ logger, - message: message || 'Unauthenticated', + message: message || "Unauthenticated", errorCode: applicationErrorCodes.unauthenticated, - userFriendlyMessage: 'You must be logged in to access this service', + userFriendlyMessage: "You must be logged in to access this service", statusCode: 401, }); } diff --git a/src/modules/errors/index.ts b/src/modules/errors/index.ts index ccdd11a..ef5423b 100644 --- a/src/modules/errors/index.ts +++ b/src/modules/errors/index.ts @@ -1 +1 @@ -export * from './ApplicationError'; +export * from "./ApplicationError"; diff --git a/src/modules/http/base/base-http-client.ts b/src/modules/http/base/base-http-client.ts index e2de6c0..db9eb83 100644 --- a/src/modules/http/base/base-http-client.ts +++ b/src/modules/http/base/base-http-client.ts @@ -1,8 +1,8 @@ -import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; +import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; -import type { BonadocsLogger } from '@bonadocs/logger'; +import type { BonadocsLogger } from "@bonadocs/logger"; -import type { ConfigService } from '../../configuration'; +import type { ConfigService } from "../../configuration"; /** * Base class for HTTP clients. This class provides methods for sending HTTP requests @@ -26,17 +26,21 @@ export default abstract class BaseHttpClient { * * @param config Axios request config for the request */ - protected async request( + protected async request< + ResponseDataType = unknown, + RequestDataType = unknown, + >( config: AxiosRequestConfig, ): Promise> { const headers = { - ...(config.data ? { 'content-type': 'application/json' } : {}), - 'trace-id': (this.logger.context.traceId || this.logger.context.loggerContextId) as string, + ...(config.data ? { "content-type": "application/json" } : {}), + "trace-id": (this.logger.context.traceId || + this.logger.context.loggerContextId) as string, // you can override the headers if you want. always use lowercase header names ...config.headers, }; - const type = this.isExternal ? 'External' : 'Internal'; + const type = this.isExternal ? "External" : "Internal"; try { const response = await this.http.request({ ...config, @@ -48,7 +52,9 @@ export default abstract class BaseHttpClient { request: { url: response.config.url, method: response.config.method, - headers: this.configService.isDebugMode() ? response.config.headers : '', + headers: this.configService.isDebugMode() + ? response.config.headers + : "", data: response.config.data, }, response: { @@ -56,9 +62,9 @@ export default abstract class BaseHttpClient { headers: this.configService.isDebugMode() ? response.headers : { - 'content-type': response.headers['content-type'], - 'content-length': response.headers['content-length'], - '...rest': '', + "content-type": response.headers["content-type"], + "content-length": response.headers["content-length"], + "...rest": "", }, data: response.data, }, diff --git a/src/modules/http/base/errors.ts b/src/modules/http/base/errors.ts index fd376d0..e921c89 100644 --- a/src/modules/http/base/errors.ts +++ b/src/modules/http/base/errors.ts @@ -4,6 +4,6 @@ export default class HttpInternalAuthError extends Error { constructor(message: string) { super(message); - this.name = 'HttpInternalAuthError'; + this.name = "HttpInternalAuthError"; } } diff --git a/src/modules/http/base/index.ts b/src/modules/http/base/index.ts index e85bce7..29f4425 100644 --- a/src/modules/http/base/index.ts +++ b/src/modules/http/base/index.ts @@ -1 +1 @@ -export { default as BaseHttpClient } from './base-http-client'; +export { default as BaseHttpClient } from "./base-http-client"; diff --git a/src/modules/http/index.ts b/src/modules/http/index.ts index 299e12a..636f2cc 100644 --- a/src/modules/http/index.ts +++ b/src/modules/http/index.ts @@ -1 +1 @@ -export * from './tenderly-http-client'; +export * from "./tenderly-http-client"; diff --git a/src/modules/http/paddle-http-client/client.ts b/src/modules/http/paddle-http-client/client.ts index 4e7704b..dacdfe7 100644 --- a/src/modules/http/paddle-http-client/client.ts +++ b/src/modules/http/paddle-http-client/client.ts @@ -1,12 +1,12 @@ -import axios from 'axios'; -import { Inject, Service } from 'typedi'; +import axios from "axios"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ConfigService, Paddle } from '../../configuration'; -import { ApplicationError } from '../../errors'; -import { BaseHttpClient } from '../base'; +import { ConfigService, Paddle } from "../../configuration"; +import { ApplicationError } from "../../errors"; +import { BaseHttpClient } from "../base"; import { AddressResponse, @@ -15,7 +15,7 @@ import { CreateTransactionRequest, CustomerResponse, TransactionResponse, -} from './types'; +} from "./types"; @Service() export class PaddleHttpClient extends BaseHttpClient { @@ -23,7 +23,7 @@ export class PaddleHttpClient extends BaseHttpClient { @Inject(diConstants.logger) logger: BonadocsLogger, @Inject() configService: ConfigService, ) { - const paddleConfigs = configService.getTransformed('paddle'); + const paddleConfigs = configService.getTransformed("paddle"); super( configService, logger, @@ -33,22 +33,29 @@ export class PaddleHttpClient extends BaseHttpClient { timeout: 60000, headers: { Authorization: `Bearer ${paddleConfigs.secretKey}`, - 'Content-Type': 'application/json', + "Content-Type": "application/json", }, }), ); } // create transaction - async createTransaction(request: CreateTransactionRequest): Promise { + async createTransaction( + request: CreateTransactionRequest, + ): Promise { // try to get customer let customer = await this.getCustomer(request.customer.email); if (!customer) { - customer = await this.createCustomer(request.customer.name, request.customer.email); + customer = await this.createCustomer( + request.customer.name, + request.customer.email, + ); } let address: AddressResponse | undefined; const addresses = await this.listCustomerAddress(customer.id); - address = addresses?.find((x) => x.country_code === request.customer.countryCode); + address = addresses?.find( + (x) => x.country_code === request.customer.countryCode, + ); if (address === null || address === undefined) { address = await this.createCustomerAddress( customer.id, @@ -58,8 +65,8 @@ export class PaddleHttpClient extends BaseHttpClient { } // create transaction const response = await this.request>({ - url: '/transactions', - method: 'POST', + url: "/transactions", + method: "POST", data: { items: [ { @@ -69,19 +76,19 @@ export class PaddleHttpClient extends BaseHttpClient { ], customer_id: customer.id, address_id: address.id, - collection_mode: 'automatic', - currency_code: 'USD', + collection_mode: "automatic", + currency_code: "USD", custom_data: request.subscription.customData, }, }); if (response.status !== 201) { - this.logger.error('Error creating transaction', response); + this.logger.error("Error creating transaction", response); throw new ApplicationError({ logger: this.logger, - message: 'Error creating paddle transaction', - errorCode: 'paddle_transaction_creation_error', - userFriendlyMessage: 'Error creating transaction', + message: "Error creating paddle transaction", + errorCode: "paddle_transaction_creation_error", + userFriendlyMessage: "Error creating transaction", }); } @@ -95,79 +102,91 @@ export class PaddleHttpClient extends BaseHttpClient { ): Promise { const response = await this.request>({ url: `/subscriptions/${subscriptionId}/charge`, - method: 'POST', + method: "POST", data: request, }); if (response.status !== 201) { - this.logger.error('Error creating one-time charge on subscription', response); + this.logger.error( + "Error creating one-time charge on subscription", + response, + ); throw new ApplicationError({ logger: this.logger, - message: 'Error creating paddle customer', - errorCode: 'paddle_customer_creation_error', - userFriendlyMessage: 'Error creating customer', + message: "Error creating paddle customer", + errorCode: "paddle_customer_creation_error", + userFriendlyMessage: "Error creating customer", }); } return response.data.data as CreateOneTimeChargeForOveragesResponse; } // get transactions by subscription id - async getTransactionsBySubscriptionId(subscriptionId: string): Promise { + async getTransactionsBySubscriptionId( + subscriptionId: string, + ): Promise { const response = await this.request>({ url: `/transactions?subscription_id=${subscriptionId}`, - method: 'GET', + method: "GET", }); if (response.status !== 200) { - this.logger.error('Error getting transactions', response); + this.logger.error("Error getting transactions", response); throw new ApplicationError({ logger: this.logger, - message: 'Error getting paddle transactions', - errorCode: 'paddle_transaction_get_error', - userFriendlyMessage: 'Error getting transactions', + message: "Error getting paddle transactions", + errorCode: "paddle_transaction_get_error", + userFriendlyMessage: "Error getting transactions", }); } return response.data.data as TransactionResponse[]; } // get transactions by id - async getTransactionById(transactionId: string): Promise { + async getTransactionById( + transactionId: string, + ): Promise { const response = await this.request>({ url: `/transactions/${transactionId}`, - method: 'GET', + method: "GET", }); if (response.status !== 200) { - this.logger.error('Error getting transactions', response); + this.logger.error("Error getting transactions", response); throw new ApplicationError({ logger: this.logger, - message: 'Error getting paddle transactions', - errorCode: 'paddle_transaction_get_error', - userFriendlyMessage: 'Error getting transactions', + message: "Error getting paddle transactions", + errorCode: "paddle_transaction_get_error", + userFriendlyMessage: "Error getting transactions", }); } return response.data.data as TransactionResponse; } - async getTransactions(transactionIds: string): Promise { + async getTransactions( + transactionIds: string, + ): Promise { const response = await this.request>({ url: `/transactions?id=${transactionIds}`, - method: 'GET', + method: "GET", }); if (response.status !== 200) { - this.logger.error('Error getting transactions', response); + this.logger.error("Error getting transactions", response); throw new ApplicationError({ logger: this.logger, - message: 'Error getting paddle transactions', - errorCode: 'paddle_transaction_get_error', - userFriendlyMessage: 'Error getting transactions', + message: "Error getting paddle transactions", + errorCode: "paddle_transaction_get_error", + userFriendlyMessage: "Error getting transactions", }); } return response.data.data as TransactionResponse[]; } // create customer - private async createCustomer(fullName: string, email: string): Promise { + private async createCustomer( + fullName: string, + email: string, + ): Promise { const response = await this.request>({ - url: '/customers', - method: 'POST', + url: "/customers", + method: "POST", data: { name: fullName, email, @@ -175,34 +194,36 @@ export class PaddleHttpClient extends BaseHttpClient { }); if (response.status !== 201) { - this.logger.error('Error creating customer', response); + this.logger.error("Error creating customer", response); throw new ApplicationError({ logger: this.logger, - message: 'Error creating paddle customer', - errorCode: 'paddle_customer_creation_error', - userFriendlyMessage: 'Error creating customer', + message: "Error creating paddle customer", + errorCode: "paddle_customer_creation_error", + userFriendlyMessage: "Error creating customer", }); } return response.data.data as CustomerResponse; } // get customer - private async getCustomer(email: string): Promise | undefined> { + private async getCustomer( + email: string, + ): Promise | undefined> { const response = await this.request>({ - url: '/customers', - method: 'GET', + url: "/customers", + method: "GET", params: { email, }, }); if (response.status !== 200) { - this.logger.error('Error getting customer', response); + this.logger.error("Error getting customer", response); throw new ApplicationError({ logger: this.logger, - message: 'Error getting paddle customer', - errorCode: 'paddle_customer_get_error', - userFriendlyMessage: 'Error getting customer', + message: "Error getting paddle customer", + errorCode: "paddle_customer_get_error", + userFriendlyMessage: "Error getting customer", }); } @@ -218,19 +239,21 @@ export class PaddleHttpClient extends BaseHttpClient { } // list customer address - private async listCustomerAddress(customerId: string): Promise { + private async listCustomerAddress( + customerId: string, + ): Promise { const response = await this.request>({ url: `customers/${customerId}/addresses`, - method: 'GET', + method: "GET", }); if (response.status !== 200) { - this.logger.error('Error getting customer', response); + this.logger.error("Error getting customer", response); throw new ApplicationError({ logger: this.logger, - message: 'Error getting paddle customer address', - errorCode: 'paddle_customer_get_error', - userFriendlyMessage: 'Error getting customer', + message: "Error getting paddle customer address", + errorCode: "paddle_customer_get_error", + userFriendlyMessage: "Error getting customer", }); } @@ -251,7 +274,7 @@ export class PaddleHttpClient extends BaseHttpClient { ): Promise { const response = await this.request>({ url: `customers/${customerId}/addresses`, - method: 'POST', + method: "POST", data: { country_code: countryCode, postal_code: postalCode, @@ -259,12 +282,12 @@ export class PaddleHttpClient extends BaseHttpClient { }); if (response.status !== 201) { - this.logger.error('Error getting customer', response); + this.logger.error("Error getting customer", response); throw new ApplicationError({ logger: this.logger, - message: 'Error creating paddle customer address', - errorCode: 'paddle_customer_get_error', - userFriendlyMessage: 'Error getting customer', + message: "Error creating paddle customer address", + errorCode: "paddle_customer_get_error", + userFriendlyMessage: "Error getting customer", }); } diff --git a/src/modules/http/paddle-http-client/index.ts b/src/modules/http/paddle-http-client/index.ts index 4ed950b..62bcdb4 100644 --- a/src/modules/http/paddle-http-client/index.ts +++ b/src/modules/http/paddle-http-client/index.ts @@ -1 +1 @@ -export { PaddleHttpClient } from './client'; +export { PaddleHttpClient } from "./client"; diff --git a/src/modules/http/paddle-http-client/types.ts b/src/modules/http/paddle-http-client/types.ts index 4fb95a3..fa8fb4d 100644 --- a/src/modules/http/paddle-http-client/types.ts +++ b/src/modules/http/paddle-http-client/types.ts @@ -49,7 +49,10 @@ export type CreateTransactionRequest = { export type CreateOneTimeChargeForOveragesRequest = { effective_from: string; - item: Record>>[]; + item: Record< + string, + Record> + >[]; custom_data: Record; }; diff --git a/src/modules/http/stripe-http-client/client.ts b/src/modules/http/stripe-http-client/client.ts index 4f1390c..be9ee39 100644 --- a/src/modules/http/stripe-http-client/client.ts +++ b/src/modules/http/stripe-http-client/client.ts @@ -1,14 +1,19 @@ -import axios from 'axios'; -import { Inject, Service } from 'typedi'; +import axios from "axios"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ConfigService, Stripe } from '../../configuration'; -import { ApplicationError } from '../../errors'; -import { BaseHttpClient } from '../base'; +import { ConfigService, Stripe } from "../../configuration"; +import { ApplicationError } from "../../errors"; +import { BaseHttpClient } from "../base"; -import { PaymentLinkRequest, PaymentLinkResponse, PriceRequest, PriceResponse } from './types'; +import { + PaymentLinkRequest, + PaymentLinkResponse, + PriceRequest, + PriceResponse, +} from "./types"; @Service() export class StripeHttpClient extends BaseHttpClient { @@ -16,23 +21,25 @@ export class StripeHttpClient extends BaseHttpClient { @Inject(diConstants.logger) logger: BonadocsLogger, @Inject() configService: ConfigService, ) { - const stripeConfigs = configService.getTransformed('stripe'); + const stripeConfigs = configService.getTransformed("stripe"); super( configService, logger, axios.create({ - baseURL: 'https://api.stripe.com/', + baseURL: "https://api.stripe.com/", validateStatus: () => true, timeout: 60000, headers: { Authorization: `Bearer ${stripeConfigs.secretKey}`, - 'Content-Type': 'application/x-www-form-urlencoded', + "Content-Type": "application/x-www-form-urlencoded", }, }), ); } - async generatePaymentLink(request: PaymentLinkRequest): Promise { + async generatePaymentLink( + request: PaymentLinkRequest, + ): Promise { const price = await this.createOrGetPrice({ nickname: request.name, unit_amount: request.amount, @@ -46,17 +53,17 @@ export class StripeHttpClient extends BaseHttpClient { }); if (!price.id) { - this.logger.error('Error creating price', price); + this.logger.error("Error creating price", price); throw new ApplicationError({ logger: this.logger, - message: 'Error creating price', - errorCode: 'stripe_price_creation_error', - userFriendlyMessage: 'Error creating price', + message: "Error creating price", + errorCode: "stripe_price_creation_error", + userFriendlyMessage: "Error creating price", }); } const response = await this.request({ - url: '/v1/payment_links', - method: 'post', + url: "/v1/payment_links", + method: "post", data: { line_items: [ { @@ -73,12 +80,12 @@ export class StripeHttpClient extends BaseHttpClient { }, }); if (response.status !== 200) { - this.logger.error('Error creating payment link', response.data); + this.logger.error("Error creating payment link", response.data); throw new ApplicationError({ logger: this.logger, - message: 'Error creating payment link', - errorCode: 'stripe_payment_link_creation_error', - userFriendlyMessage: 'Error creating payment link', + message: "Error creating payment link", + errorCode: "stripe_payment_link_creation_error", + userFriendlyMessage: "Error creating payment link", }); } return { @@ -90,8 +97,8 @@ export class StripeHttpClient extends BaseHttpClient { async createOrGetPrice(request: PriceRequest): Promise { // try to search price const response = await this.request>({ - url: '/v1/prices/search', - method: 'get', + url: "/v1/prices/search", + method: "get", params: { query: `active:'true' AND nickname:'${request.nickname}' AND currency:'${request.currency}' AND unit_amount:${request.unit_amount}`, }, @@ -101,8 +108,8 @@ export class StripeHttpClient extends BaseHttpClient { } // if not found, create price const createResponse = await this.request({ - url: '/v1/prices', - method: 'post', + url: "/v1/prices", + method: "post", data: { unit_amount: request.unit_amount, currency: request.currency, @@ -112,12 +119,12 @@ export class StripeHttpClient extends BaseHttpClient { }, }); if (createResponse.status !== 200 && createResponse.data.id) { - this.logger.error('Error creating price', createResponse.data); + this.logger.error("Error creating price", createResponse.data); throw new ApplicationError({ logger: this.logger, - message: 'Error creating price', - errorCode: 'stripe_price_creation_error', - userFriendlyMessage: 'Error creating price', + message: "Error creating price", + errorCode: "stripe_price_creation_error", + userFriendlyMessage: "Error creating price", }); } return createResponse.data; diff --git a/src/modules/http/stripe-http-client/index.ts b/src/modules/http/stripe-http-client/index.ts index 70f3c79..c9240f8 100644 --- a/src/modules/http/stripe-http-client/index.ts +++ b/src/modules/http/stripe-http-client/index.ts @@ -1,2 +1,2 @@ -export * from './types'; -export * from './client'; +export * from "./types"; +export * from "./client"; diff --git a/src/modules/http/tenderly-http-client/client.ts b/src/modules/http/tenderly-http-client/client.ts index c46a766..57ef0e9 100644 --- a/src/modules/http/tenderly-http-client/client.ts +++ b/src/modules/http/tenderly-http-client/client.ts @@ -1,15 +1,20 @@ -import axios from 'axios'; -import { ZeroAddress } from 'ethers'; -import { Inject, Service } from 'typedi'; +import axios from "axios"; +import { ZeroAddress } from "ethers"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ConfigService } from '../../configuration'; -import { Tenderly } from '../../configuration/config.interface'; -import { BaseHttpClient } from '../base'; +import { ConfigService } from "../../configuration"; +import { Tenderly } from "../../configuration/config.interface"; +import { BaseHttpClient } from "../base"; -import { BundleSimulationResult, EVMCall, SimulationResponseData, SimulationResult } from './types'; +import { + BundleSimulationResult, + EVMCall, + SimulationResponseData, + SimulationResult, +} from "./types"; @Service() export class TenderlyApiClient extends BaseHttpClient { @@ -17,7 +22,7 @@ export class TenderlyApiClient extends BaseHttpClient { @Inject() configService: ConfigService, @Inject(diConstants.logger) logger: BonadocsLogger, ) { - const tenderlyConfigs = configService.getTransformed('tenderly'); + const tenderlyConfigs = configService.getTransformed("tenderly"); super( configService, logger, @@ -26,7 +31,7 @@ export class TenderlyApiClient extends BaseHttpClient { validateStatus: () => true, timeout: 60000, headers: { - 'X-Access-Key': tenderlyConfigs.accessKey, + "X-Access-Key": tenderlyConfigs.accessKey, }, }), ); @@ -38,8 +43,8 @@ export class TenderlyApiClient extends BaseHttpClient { ): Promise { try { const response = await this.request({ - url: '/simulate', - method: 'post', + url: "/simulate", + method: "post", data: this.callToSimulationData(chainId, call), }); @@ -51,9 +56,12 @@ export class TenderlyApiClient extends BaseHttpClient { return this.normalizeSimulationResult(response.data); } catch (error) { if (axios.isAxiosError(error)) { - this.logger.error('Axios error during simulation request', error.message); + this.logger.error( + "Axios error during simulation request", + error.message, + ); } else { - this.logger.error('Unexpected error during simulation request', error); + this.logger.error("Unexpected error during simulation request", error); } return undefined; } @@ -65,9 +73,13 @@ export class TenderlyApiClient extends BaseHttpClient { ): Promise { try { const response = await this.request({ - url: '/simulate-bundle', - method: 'post', - data: { simulations: calls.map((call) => this.callToSimulationData(chainId, call)) }, + url: "/simulate-bundle", + method: "post", + data: { + simulations: calls.map((call) => + this.callToSimulationData(chainId, call), + ), + }, }); if (response.status !== 200) { @@ -80,9 +92,15 @@ export class TenderlyApiClient extends BaseHttpClient { ); } catch (error) { if (axios.isAxiosError(error)) { - this.logger.error('Axios error during bundle simulation request', error.message); + this.logger.error( + "Axios error during bundle simulation request", + error.message, + ); } else { - this.logger.error('Unexpected error during bundle simulation request', error); + this.logger.error( + "Unexpected error during bundle simulation request", + error, + ); } return undefined; } @@ -102,7 +120,7 @@ export class TenderlyApiClient extends BaseHttpClient { /* Simulation Configuration */ save: false, save_if_fails: false, - simulation_type: 'quick', + simulation_type: "quick", network_id: chainId.toString(10), state_objects: stateObjects, @@ -116,14 +134,17 @@ export class TenderlyApiClient extends BaseHttpClient { }; } - private normalizeSimulationResult(data: SimulationResult): SimulationResponseData { + private normalizeSimulationResult( + data: SimulationResult, + ): SimulationResponseData { const resultData = data.transaction; return { error: !resultData.error_info ? undefined : { address: resultData.error_info.address, - message: resultData.error_info.error_message || 'execution reverted', + message: + resultData.error_info.error_message || "execution reverted", }, receipt: { from: resultData.from!, @@ -140,7 +161,7 @@ export class TenderlyApiClient extends BaseHttpClient { cumulativeGasUsed: BigInt(resultData.cumulative_gas_used!), gasPrice: BigInt(resultData.gas_price!), effectiveGasPrice: BigInt(resultData.effective_gas_price!), - logsBloom: '0x', + logsBloom: "0x", logs: resultData.transaction_info!.logs?.map((l, i) => ({ data: l.raw!.data!, diff --git a/src/modules/http/tenderly-http-client/index.ts b/src/modules/http/tenderly-http-client/index.ts index 8b3a19c..22713c5 100644 --- a/src/modules/http/tenderly-http-client/index.ts +++ b/src/modules/http/tenderly-http-client/index.ts @@ -1,3 +1,3 @@ -export * from './client'; +export * from "./client"; -export * from './types'; +export * from "./types"; diff --git a/src/modules/http/tenderly-http-client/types.ts b/src/modules/http/tenderly-http-client/types.ts index 0c465a9..b9444cb 100644 --- a/src/modules/http/tenderly-http-client/types.ts +++ b/src/modules/http/tenderly-http-client/types.ts @@ -1,4 +1,4 @@ -import { BigNumberish, TransactionReceiptParams } from 'ethers'; +import { BigNumberish, TransactionReceiptParams } from "ethers"; export interface SimulationAccountOverrides { address: string; diff --git a/src/modules/index.ts b/src/modules/index.ts index ebd65e9..bc23b9f 100644 --- a/src/modules/index.ts +++ b/src/modules/index.ts @@ -1,9 +1,9 @@ -export { ConfigService, getConfigService } from './configuration'; -export { UserRepository } from './repositories'; -export * from './auth'; -export * from './project'; -export * from './contract'; -export * from './subscription'; -export * from './tenderly'; -export * from './billing'; -export * from './coupon'; +export { ConfigService, getConfigService } from "./configuration"; +export { UserRepository } from "./repositories"; +export * from "./auth"; +export * from "./project"; +export * from "./contract"; +export * from "./subscription"; +export * from "./tenderly"; +export * from "./billing"; +export * from "./coupon"; diff --git a/src/modules/mailing/index.ts b/src/modules/mailing/index.ts index 1ca41b0..4ce5e35 100644 --- a/src/modules/mailing/index.ts +++ b/src/modules/mailing/index.ts @@ -1,3 +1,3 @@ -export { MailgunSender } from './mailgun'; -export { MailTemplate } from './types'; -export type { MailSender } from './types.js'; +export { MailgunSender } from "./mailgun"; +export { MailTemplate } from "./types"; +export type { MailSender } from "./types.js"; diff --git a/src/modules/mailing/mailgun.ts b/src/modules/mailing/mailgun.ts index 9b4d00e..ce84940 100644 --- a/src/modules/mailing/mailgun.ts +++ b/src/modules/mailing/mailgun.ts @@ -1,14 +1,14 @@ -import axios, { AxiosInstance } from 'axios'; -import qs from 'qs'; -import { Inject, Service } from 'typedi'; +import axios, { AxiosInstance } from "axios"; +import qs from "qs"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { MailGun } from '../configuration/config.interface'; -import { ConfigService } from '../configuration/config.service'; +import { MailGun } from "../configuration/config.interface"; +import { ConfigService } from "../configuration/config.service"; -import { MailError, MailSender, SenderConfig, SendOptions } from './types'; +import { MailError, MailSender, SenderConfig, SendOptions } from "./types"; type MailgunOptions = { domain: string; @@ -32,11 +32,11 @@ export class MailgunSender implements MailSender { this.httpClient = axios.create({ baseURL: `https://api.mailgun.net/v3/${this._options.domain}`, auth: { - username: 'api', + username: "api", password: this._options!.apiKey, }, headers: { - 'Content-Type': 'application/x-www-form-urlencoded', + "Content-Type": "application/x-www-form-urlencoded", }, validateStatus: () => true, }); @@ -45,11 +45,14 @@ export class MailgunSender implements MailSender { private loadConfig(): MailgunOptions { return { - domain: this.configService.getTransformed('mail').domain, - apiKey: this.configService.getTransformed('mail').apiKey, + domain: this.configService.getTransformed("mail").domain, + apiKey: this.configService.getTransformed("mail").apiKey, defaultSender: { - email: this.configService.getTransformed('mail').defaultSender.email, - name: this.configService.getTransformed('mail').defaultSender.name, + email: + this.configService.getTransformed("mail").defaultSender + .email, + name: this.configService.getTransformed("mail").defaultSender + .name, }, }; } @@ -60,27 +63,36 @@ export class MailgunSender implements MailSender { const body = { from: `${sender.name} <${sender.email}>`, - to: Array.from(to).join(','), - cc: Array.from(cc).join(','), - bcc: Array.from(bcc).join(','), + to: Array.from(to).join(","), + cc: Array.from(cc).join(","), + bcc: Array.from(bcc).join(","), subject: options.subject, template: options.template, - 'h:X-Mailgun-Variables': JSON.stringify(options.data), + "h:X-Mailgun-Variables": JSON.stringify(options.data), }; - this.logger.info('Sending email with mailgun', { params: body }); - const response = await this.httpClient.post('/messages', qs.stringify(body)); + this.logger.info("Sending email with mailgun", { params: body }); + const response = await this.httpClient.post( + "/messages", + qs.stringify(body), + ); if (response.status !== 200) { - this.logger.error('Failed to send email with mailgun', { + this.logger.error("Failed to send email with mailgun", { params: { status: response.status, data: response.data }, }); - throw new MailError('Failed to send email', response.status, response.data); + throw new MailError( + "Failed to send email", + response.status, + response.data, + ); } } private normalizeEmails(options: SendOptions) { const to = new Set( - Array.isArray(options.receiver.to) ? options.receiver.to : [options.receiver.to], + Array.isArray(options.receiver.to) + ? options.receiver.to + : [options.receiver.to], ); const cc = new Set(options.receiver.cc || []); diff --git a/src/modules/mailing/types.ts b/src/modules/mailing/types.ts index bf6f9a8..ae5e194 100644 --- a/src/modules/mailing/types.ts +++ b/src/modules/mailing/types.ts @@ -1,8 +1,8 @@ export enum MailTemplate { - ProjectInvitation = 'bonadocs_invite_project_member_template', - ProjectJoined = 'bonadocs_project_joined_template', - InvoicePaymentReminder = 'bonadocs_invoice_payment_reminder_template', - OveragesNotification = 'bonadocs_overages_notification_template', + ProjectInvitation = "bonadocs_invite_project_member_template", + ProjectJoined = "bonadocs_project_joined_template", + InvoicePaymentReminder = "bonadocs_invoice_payment_reminder_template", + OveragesNotification = "bonadocs_overages_notification_template", } type MailData = Record; diff --git a/src/modules/project/dto/accept-project-invitation.dto.ts b/src/modules/project/dto/accept-project-invitation.dto.ts index 73943be..951aa5b 100644 --- a/src/modules/project/dto/accept-project-invitation.dto.ts +++ b/src/modules/project/dto/accept-project-invitation.dto.ts @@ -1,8 +1,8 @@ -import { IsNotEmpty } from 'class-validator'; +import { IsNotEmpty } from "class-validator"; export class AcceptProjectInvitationDto { @IsNotEmpty({ - message: 'Invitation token not provided', + message: "Invitation token not provided", }) token: string; } diff --git a/src/modules/project/dto/cancel-project-invite.dto.ts b/src/modules/project/dto/cancel-project-invite.dto.ts index 7316306..b619908 100644 --- a/src/modules/project/dto/cancel-project-invite.dto.ts +++ b/src/modules/project/dto/cancel-project-invite.dto.ts @@ -1,11 +1,11 @@ -import { IsNotEmpty, Matches } from 'class-validator'; +import { IsNotEmpty, Matches } from "class-validator"; export class CancelProjectInvitationDto { @IsNotEmpty({ - message: 'Email address not provided', + message: "Email address not provided", }) @Matches(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, { - message: 'Invalid email address provided', + message: "Invalid email address provided", }) emailAddress: string; } diff --git a/src/modules/project/dto/create-project-collection.dto.ts b/src/modules/project/dto/create-project-collection.dto.ts index 5fc7626..92741de 100644 --- a/src/modules/project/dto/create-project-collection.dto.ts +++ b/src/modules/project/dto/create-project-collection.dto.ts @@ -1,23 +1,23 @@ -import { IsBoolean, IsNotEmpty, IsString, Matches } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsString, Matches } from "class-validator"; export class CreateProjectCollectionDto { @IsString() @IsNotEmpty({ - message: 'Collection name not provided', + message: "Collection name not provided", }) @Matches(/^[a-zA-Z0-9\s-_]{3,50}$/, { - message: 'Invalid collection name', + message: "Invalid collection name", }) name: string; @IsNotEmpty({ - message: 'isPublic is required', + message: "isPublic is required", }) @IsBoolean() isPublic: boolean; @IsNotEmpty({ - message: 'Collection data is required', + message: "Collection data is required", }) collectionData: Record; } diff --git a/src/modules/project/dto/create-project.dto.ts b/src/modules/project/dto/create-project.dto.ts index 21f1ca0..47f6940 100644 --- a/src/modules/project/dto/create-project.dto.ts +++ b/src/modules/project/dto/create-project.dto.ts @@ -1,31 +1,38 @@ -import { IsNotEmpty, IsString, Matches, MaxLength, MinLength } from 'class-validator'; +import { + IsNotEmpty, + IsString, + Matches, + MaxLength, + MinLength, +} from "class-validator"; export class CreateProjectDto { @IsString() @IsNotEmpty({ - message: 'Project name not provided', + message: "Project name not provided", }) @Matches(/^[a-zA-Z0-9_\- ]+$/, { - message: 'Project name must contain only letters, numbers, underscores, dashes and spaces', + message: + "Project name must contain only letters, numbers, underscores, dashes and spaces", }) @MinLength(3, { - message: 'Project name must be more than 3 characters', + message: "Project name must be more than 3 characters", }) @MaxLength(20, { - message: 'Project name must be more than 3 characters', + message: "Project name must be more than 3 characters", }) name: string; @IsString() @IsNotEmpty({ - message: 'Project slug not provided', + message: "Project slug not provided", }) @Matches(/^[a-z][a-z0-9]+(?:-[a-z0-9]+)*$/, { message: - 'Project slug must start with a small letter and contain only small letters, numbers and dashes', + "Project slug must start with a small letter and contain only small letters, numbers and dashes", }) @MaxLength(30, { - message: 'Project slug must be less than 50 characters', + message: "Project slug must be less than 50 characters", }) slug: string; } diff --git a/src/modules/project/dto/generate-project-api-key.dto.ts b/src/modules/project/dto/generate-project-api-key.dto.ts index f4791a5..f753fed 100644 --- a/src/modules/project/dto/generate-project-api-key.dto.ts +++ b/src/modules/project/dto/generate-project-api-key.dto.ts @@ -1,22 +1,22 @@ -import { Type } from 'class-transformer'; -import { IsDate, IsNotEmpty, IsString, Matches } from 'class-validator'; +import { Type } from "class-transformer"; +import { IsDate, IsNotEmpty, IsString, Matches } from "class-validator"; export class GenerateProjectApiKeyDto { @IsString() @IsNotEmpty({ - message: 'Api Key name not provided', + message: "Api Key name not provided", }) @Matches(/^[a-zA-Z0-9_\- ]{2,50}$/, { message: - 'A unique name is required for the API key. The name must be between 2-50 characters long.', + "A unique name is required for the API key. The name must be between 2-50 characters long.", }) name: string; @IsNotEmpty({ - message: 'Api Key expire time not provided', + message: "Api Key expire time not provided", }) @Type(() => Date) - @IsDate({ message: 'expireAt must be a valid Date' }) + @IsDate({ message: "expireAt must be a valid Date" }) expireAt: Date; force: boolean; diff --git a/src/modules/project/dto/index.ts b/src/modules/project/dto/index.ts index 8bdeb97..f3afa80 100644 --- a/src/modules/project/dto/index.ts +++ b/src/modules/project/dto/index.ts @@ -1,8 +1,8 @@ -export { GenerateProjectApiKeyDto } from './generate-project-api-key.dto'; -export { CreateProjectDto } from './create-project.dto'; -export { CreateProjectCollectionDto } from './create-project-collection.dto'; -export { RegisterDeviceDto } from './register-device.dto'; -export { UpdateProjectCollectionDto } from './update-project-collection.dto'; -export { CancelProjectInvitationDto } from './cancel-project-invite.dto'; -export { AcceptProjectInvitationDto } from './accept-project-invitation.dto'; -export { UpdateProjectUserPermissionDto } from './update-project-user-permission.dto'; +export { GenerateProjectApiKeyDto } from "./generate-project-api-key.dto"; +export { CreateProjectDto } from "./create-project.dto"; +export { CreateProjectCollectionDto } from "./create-project-collection.dto"; +export { RegisterDeviceDto } from "./register-device.dto"; +export { UpdateProjectCollectionDto } from "./update-project-collection.dto"; +export { CancelProjectInvitationDto } from "./cancel-project-invite.dto"; +export { AcceptProjectInvitationDto } from "./accept-project-invitation.dto"; +export { UpdateProjectUserPermissionDto } from "./update-project-user-permission.dto"; diff --git a/src/modules/project/dto/invite-project-user.dto.ts b/src/modules/project/dto/invite-project-user.dto.ts index 362618e..47316fe 100644 --- a/src/modules/project/dto/invite-project-user.dto.ts +++ b/src/modules/project/dto/invite-project-user.dto.ts @@ -1,18 +1,18 @@ -import { IsNotEmpty, Matches } from 'class-validator'; +import { IsNotEmpty, Matches } from "class-validator"; -import { PermissionName } from '../../repositories/projects/util'; +import { PermissionName } from "../../repositories/projects/util"; export class InviteProjectUserDto { @IsNotEmpty({ - message: 'Email address not provided', + message: "Email address not provided", }) @Matches(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, { - message: 'Invalid email address provided', + message: "Invalid email address provided", }) emailAddress: string; @IsNotEmpty({ - message: 'Member name not provided', + message: "Member name not provided", }) memberName: string; diff --git a/src/modules/project/dto/register-device.dto.ts b/src/modules/project/dto/register-device.dto.ts index d490083..6bdf12f 100644 --- a/src/modules/project/dto/register-device.dto.ts +++ b/src/modules/project/dto/register-device.dto.ts @@ -1,13 +1,13 @@ -import { IsNotEmpty } from 'class-validator'; +import { IsNotEmpty } from "class-validator"; export class RegisterDeviceDto { @IsNotEmpty({ - message: 'Token not provided', + message: "Token not provided", }) token: string; @IsNotEmpty({ - message: 'Timestamp not provided', + message: "Timestamp not provided", }) timestamp: number; } diff --git a/src/modules/project/dto/update-project-collection.dto.ts b/src/modules/project/dto/update-project-collection.dto.ts index 60d582d..e923d03 100644 --- a/src/modules/project/dto/update-project-collection.dto.ts +++ b/src/modules/project/dto/update-project-collection.dto.ts @@ -1,17 +1,17 @@ -import { IsBoolean, IsNotEmpty, IsString, Matches } from 'class-validator'; +import { IsBoolean, IsNotEmpty, IsString, Matches } from "class-validator"; export class UpdateProjectCollectionDto { @IsString() @IsNotEmpty({ - message: 'Collection name not provided', + message: "Collection name not provided", }) @Matches(/^[a-zA-Z0-9\s-_]{3,50}$/, { - message: 'Invalid collection name', + message: "Invalid collection name", }) name: string; @IsNotEmpty({ - message: 'isPublic is required', + message: "isPublic is required", }) @IsBoolean() isPublic: boolean; diff --git a/src/modules/project/dto/update-project-user-permission.dto.ts b/src/modules/project/dto/update-project-user-permission.dto.ts index 9aaf3ad..5d11933 100644 --- a/src/modules/project/dto/update-project-user-permission.dto.ts +++ b/src/modules/project/dto/update-project-user-permission.dto.ts @@ -1,4 +1,4 @@ -import { PermissionName } from '../../repositories/projects/util'; +import { PermissionName } from "../../repositories/projects/util"; export class UpdateProjectUserPermissionDto { permissions: PermissionName[]; diff --git a/src/modules/project/index.ts b/src/modules/project/index.ts index 34b4d61..bc700f6 100644 --- a/src/modules/project/index.ts +++ b/src/modules/project/index.ts @@ -1 +1 @@ -export { ProjectController } from './project.controller'; +export { ProjectController } from "./project.controller"; diff --git a/src/modules/project/project.controller.ts b/src/modules/project/project.controller.ts index d108c49..0f61f32 100644 --- a/src/modules/project/project.controller.ts +++ b/src/modules/project/project.controller.ts @@ -1,4 +1,4 @@ -import { Request } from 'express'; +import { Request } from "express"; import { Body, Delete, @@ -9,10 +9,10 @@ import { Put, QueryParam, Req, -} from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +} from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { JsonResponse } from '../shared'; +import { JsonResponse } from "../shared"; import { CancelProjectInvitationDto, @@ -22,8 +22,8 @@ import { RegisterDeviceDto, UpdateProjectCollectionDto, UpdateProjectUserPermissionDto, -} from './dto'; -import { InviteProjectUserDto } from './dto/invite-project-user.dto'; +} from "./dto"; +import { InviteProjectUserDto } from "./dto/invite-project-user.dto"; import { CancelProjectInvitationRequest, CreateProjectCollectionRequest, @@ -43,71 +43,78 @@ import { ProjectDetailsResponse, UpdateProjectCollectionRequest, UpdateProjectUserPermissionRequest, -} from './project.interface'; -import { ProjectService } from './project.service'; +} from "./project.interface"; +import { ProjectService } from "./project.service"; @Service() -@JsonController('/projects') +@JsonController("/projects") export class ProjectController { constructor(@Inject() private readonly projectService: ProjectService) {} - @Post('/') + @Post("/") async create( @Body({ validate: true }) payload: CreateProjectDto, @Req() request: Request, ): Promise> { const authData = request.auth; - const response = await this.projectService.create(payload as CreateProjectRequest, authData); + const response = await this.projectService.create( + payload as CreateProjectRequest, + authData, + ); return { data: response, - status: 'successful', - message: 'Created Project successfully', + status: "successful", + message: "Created Project successfully", }; } - @Get('/') - async list(@Req() request: Request): Promise> { + @Get("/") + async list( + @Req() request: Request, + ): Promise> { const authData = request.auth; const response = await this.projectService.list(authData); return { data: response, - status: 'successful', - message: 'Project List Successfully', + status: "successful", + message: "Project List Successfully", }; } - @Get('/:projectId') + @Get("/:projectId") async get( - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, @Req() request: Request, ): Promise> { const authData = request.auth; const response = await this.projectService.get(projectId, authData); return { data: response, - status: 'successful', - message: 'Project Get Successfully', + status: "successful", + message: "Project Get Successfully", }; } - @Delete('/:projectId') + @Delete("/:projectId") async delete( - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, @Req() request: Request, ): Promise { const authData = request.auth; const response = await this.projectService.delete(projectId, authData); return { - status: response ? 'successful' : 'error', - message: response ? 'Project Delete Successfully' : 'Unable to Delete Project', + status: response ? "successful" : "error", + message: response + ? "Project Delete Successfully" + : "Unable to Delete Project", }; } - @Post('/:projectId/api-key') + @Post("/:projectId/api-key") async generateApiKey( @Body({ validate: true }) payload: GenerateProjectApiKeyDto, @Req() request: Request, - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, ): Promise> { const authData = request.auth; const response = await this.projectService.generateApiKey( @@ -116,46 +123,46 @@ export class ProjectController { payload as GenerateProjectApiKeyRequest, ); return { - status: 'successful', - message: 'Api Key Generated Successfully', + status: "successful", + message: "Api Key Generated Successfully", data: response, }; } - @Get('/:projectId/api-key') + @Get("/:projectId/api-key") async listApiKeys( - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, @Req() request: Request, ): Promise> { const authData = request.auth; const response = await this.projectService.listApiKeys(projectId, authData); return { - status: 'successful', - message: 'Api Key Listed Successfully', + status: "successful", + message: "Api Key Listed Successfully", data: response, }; } - @Delete('/:projectId/api-key/:apiKeyId') + @Delete("/:projectId/api-key/:apiKeyId") async revokeApiKey( @Req() request: Request, - @Param('projectId') projectId: number, - @Param('apiKeyId') apiKeyId: number, + @Param("projectId") projectId: number, + @Param("apiKeyId") apiKeyId: number, ): Promise { const authData = request.auth; this.projectService.revokeApiKey(projectId, apiKeyId, authData); return { - status: 'successful', - message: 'Successfully Revoked ApiKey', + status: "successful", + message: "Successfully Revoked ApiKey", }; } - @Post('/:projectId/collections') + @Post("/:projectId/collections") async createCollection( @Req() request: Request, @Body({ validate: true }) payload: CreateProjectCollectionDto, - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, ): Promise> { const authData = request.auth; const response = await this.projectService.createCollection( @@ -164,60 +171,70 @@ export class ProjectController { payload as CreateProjectCollectionRequest, ); return { - status: 'successful', - message: 'Project Collection Create Successfully', + status: "successful", + message: "Project Collection Create Successfully", data: response, }; } - @Get('/:projectId/collections') + @Get("/:projectId/collections") async listCollection( @Req() request: Request, - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, ): Promise> { const authData = request.auth; - const response = await this.projectService.listCollections(projectId, authData); + const response = await this.projectService.listCollections( + projectId, + authData, + ); return { - status: 'successful', - message: 'Project Collection List Successfully', + status: "successful", + message: "Project Collection List Successfully", data: response, }; } - @Get('/:projectId/collections/:collectionId') + @Get("/:projectId/collections/:collectionId") async getCollection( @Req() request: Request, - @Param('projectId') projectId: number, - @Param('collectionId') collectionId: number, + @Param("projectId") projectId: number, + @Param("collectionId") collectionId: number, ): Promise> { const authData = request.auth; - const response = await this.projectService.getCollection(projectId, collectionId, authData); + const response = await this.projectService.getCollection( + projectId, + collectionId, + authData, + ); return { - status: 'successful', - message: 'Project Collection Get Successfully', + status: "successful", + message: "Project Collection Get Successfully", data: response, }; } - @Get('/:projectId/collections/:collectionId/data') + @Get("/:projectId/collections/:collectionId/data") async getCollectionData( - @Param('projectId') projectId: number, - @Param('collectionId') collectionId: number, + @Param("projectId") projectId: number, + @Param("collectionId") collectionId: number, ): Promise> { - const response = await this.projectService.getCollectionData(projectId, collectionId); + const response = await this.projectService.getCollectionData( + projectId, + collectionId, + ); return { - status: 'successful', - message: 'Project Collection Data Get Successfully', + status: "successful", + message: "Project Collection Data Get Successfully", data: response, }; } - @Put('/:projectId/collections/:collectionId') + @Put("/:projectId/collections/:collectionId") async updateCollection( @Req() request: Request, - @Param('projectId') projectId: number, - @Param('collectionId') collectionId: number, + @Param("projectId") projectId: number, + @Param("collectionId") collectionId: number, @Body({ validate: true }) payload: UpdateProjectCollectionDto, ): Promise { const authData = request.auth; @@ -228,30 +245,34 @@ export class ProjectController { payload as UpdateProjectCollectionRequest, ); return { - status: 'successful', - message: 'Project Collection Updated Successfully', + status: "successful", + message: "Project Collection Updated Successfully", }; } - @Delete('/:projectId/collections/:collectionId') + @Delete("/:projectId/collections/:collectionId") async deleteCollection( @Req() request: Request, - @Param('projectId') projectId: number, - @Param('collectionId') collectionId: number, + @Param("projectId") projectId: number, + @Param("collectionId") collectionId: number, ): Promise { const authData = request.auth; - await this.projectService.deleteCollection(projectId, collectionId, authData); + await this.projectService.deleteCollection( + projectId, + collectionId, + authData, + ); return { - status: 'successful', - message: 'Project Collection Deleted Successfully', + status: "successful", + message: "Project Collection Deleted Successfully", }; } - @Put('/:projectId/collections/:collectionId/register-device') + @Put("/:projectId/collections/:collectionId/register-device") async registerDevice( @Req() request: Request, - @Param('projectId') projectId: number, - @Param('collectionId') collectionId: number, + @Param("projectId") projectId: number, + @Param("collectionId") collectionId: number, @Body({ validate: true }) payload: RegisterDeviceDto, ): Promise { const authData = request.auth; @@ -262,15 +283,17 @@ export class ProjectController { payload, ); return { - status: response ? 'successful' : 'error', - message: response ? 'Device Successfully Registered' : 'Unable to register device', + status: response ? "successful" : "error", + message: response + ? "Device Successfully Registered" + : "Unable to register device", }; } - @Post('/:projectId/invitations') + @Post("/:projectId/invitations") async inviteProjectUser( @Req() request: Request, - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, @Body({ validate: true }) payload: InviteProjectUserDto, ): Promise { await this.projectService.inviteUser( @@ -279,29 +302,29 @@ export class ProjectController { payload as InviteProjectMemberRequest, ); return { - status: 'successful', - message: 'Invitation Successfully Sent', + status: "successful", + message: "Invitation Successfully Sent", }; } - @Post('/accept-invitations') + @Post("/accept-invitations") async acceptProjectInvitation( @Req() request: Request, - @QueryParam('token') token: string, + @QueryParam("token") token: string, ): Promise { await this.projectService.acceptInvite(token, request.auth); return { - status: 'successful', - message: 'Invitation Accepted Successfully', + status: "successful", + message: "Invitation Accepted Successfully", }; } - @Delete('/:projectId/invitations/cancel') + @Delete("/:projectId/invitations/cancel") async cancelProjectInvitation( @Req() request: Request, @Body({ validate: true }) payload: CancelProjectInvitationDto, - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, ): Promise { await this.projectService.cancelInvite( projectId, @@ -309,55 +332,61 @@ export class ProjectController { payload as CancelProjectInvitationRequest, ); return { - status: 'successful', - message: 'Invitation CCancelled Successfully', + status: "successful", + message: "Invitation CCancelled Successfully", }; } - @Get('/:projectId/invitations') + @Get("/:projectId/invitations") async listProjectInvitations( @Req() request: Request, - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, ): Promise> { - const response = await this.projectService.listInvites(projectId, request.auth); + const response = await this.projectService.listInvites( + projectId, + request.auth, + ); return { - status: 'successful', - message: 'Project Invitation List Successfully', + status: "successful", + message: "Project Invitation List Successfully", data: response, }; } - @Get('/:projectId/users') + @Get("/:projectId/users") async listProjectUser( @Req() request: Request, - @Param('projectId') projectId: number, + @Param("projectId") projectId: number, ): Promise> { - const response = await this.projectService.listUsers(projectId, request.auth); + const response = await this.projectService.listUsers( + projectId, + request.auth, + ); return { - status: 'successful', - message: 'Project Users List Successfully', + status: "successful", + message: "Project Users List Successfully", data: response, }; } - @Delete('/:projectId/users/:userId') + @Delete("/:projectId/users/:userId") async removeProjectUser( @Req() request: Request, - @Param('projectId') projectId: number, - @Param('userId') userId: number, + @Param("projectId") projectId: number, + @Param("userId") userId: number, ): Promise> { await this.projectService.removeUser(projectId, userId, request.auth); return { - status: 'successful', - message: 'Project Users Removed Successfully', + status: "successful", + message: "Project Users Removed Successfully", }; } - @Put('/:projectId/users/:userId/permissions') + @Put("/:projectId/users/:userId/permissions") async updateProjectUser( @Req() request: Request, - @Param('projectId') projectId: number, - @Param('userId') userId: number, + @Param("projectId") projectId: number, + @Param("userId") userId: number, @Body({ validate: true }) payload: UpdateProjectUserPermissionDto, ): Promise> { await this.projectService.updateUserPermission( @@ -367,8 +396,8 @@ export class ProjectController { request.auth, ); return { - status: 'successful', - message: 'Project Users Updated Successfully', + status: "successful", + message: "Project Users Updated Successfully", }; } } diff --git a/src/modules/project/project.interface.ts b/src/modules/project/project.interface.ts index 0e32b00..fe44ce1 100644 --- a/src/modules/project/project.interface.ts +++ b/src/modules/project/project.interface.ts @@ -4,9 +4,9 @@ import { ProjectDetailsDTO, ProjectForUserDTO, ProjectInvitationsDto, -} from '../repositories/projects/types'; -import { PermissionName } from '../repositories/projects/util'; -import { UserData } from '../repositories/users/types'; +} from "../repositories/projects/types"; +import { PermissionName } from "../repositories/projects/util"; +import { UserData } from "../repositories/users/types"; export interface CreateProjectResponse { id: number; diff --git a/src/modules/project/project.service.test.ts b/src/modules/project/project.service.test.ts index efababa..3c44c98 100644 --- a/src/modules/project/project.service.test.ts +++ b/src/modules/project/project.service.test.ts @@ -1,21 +1,21 @@ -import 'reflect-metadata'; +import "reflect-metadata"; -import { beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { generateMockObject, getMockLogger } from '../../test/util'; -import { AuthService } from '../auth/auth.service'; -import { ConfigService } from '../configuration/config.service'; -import { ApplicationError } from '../errors/ApplicationError'; -import { MailgunSender } from '../mailing/mailgun'; -import { ProjectRepository } from '../repositories/projects/project.repository'; -import { UserRepository } from '../repositories/users/user.repository'; -import { Storage } from '../storage'; +import { generateMockObject, getMockLogger } from "../../test/util"; +import { AuthService } from "../auth/auth.service"; +import { ConfigService } from "../configuration/config.service"; +import { ApplicationError } from "../errors/ApplicationError"; +import { MailgunSender } from "../mailing/mailgun"; +import { ProjectRepository } from "../repositories/projects/project.repository"; +import { UserRepository } from "../repositories/users/user.repository"; +import { Storage } from "../storage"; -import { ProjectService } from './project.service'; +import { ProjectService } from "./project.service"; -describe('ProjectService', () => { +describe("ProjectService", () => { let projectService: ProjectService; let authService: jest.Mocked; let projectRepository: jest.Mocked; @@ -27,40 +27,43 @@ describe('ProjectService', () => { beforeEach(() => { logger = getMockLogger(); - authService = generateMockObject('validateJWT', 'generateJWT'); + authService = generateMockObject("validateJWT", "generateJWT"); projectRepository = generateMockObject( - 'createProject', - 'getProjectsByUserId', - 'checkUserPermission', - 'getProjectDetails', - 'deleteProject', - 'generateApiKey', - 'revokeAPIKey', - 'getProjectById', - 'addProjectCollection', - 'getProjectCollections', - 'getProjectCollectionById', - 'updateProjectCollection', - 'acceptProjectInvitation', - 'acceptProjectInvitation', - 'getInvitationPermissionFlags', - 'addUserToProject', - 'inviteUserToProject', - 'cancelProjectInvitation', - 'listProjectInvitations', - 'getProjectUsers', - 'removeProjectUser', - 'updateProjectUserPermissions', - 'getProjectBySlug', + "createProject", + "getProjectsByUserId", + "checkUserPermission", + "getProjectDetails", + "deleteProject", + "generateApiKey", + "revokeAPIKey", + "getProjectById", + "addProjectCollection", + "getProjectCollections", + "getProjectCollectionById", + "updateProjectCollection", + "acceptProjectInvitation", + "acceptProjectInvitation", + "getInvitationPermissionFlags", + "addUserToProject", + "inviteUserToProject", + "cancelProjectInvitation", + "listProjectInvitations", + "getProjectUsers", + "removeProjectUser", + "updateProjectUserPermissions", + "getProjectBySlug", + ); + configService = generateMockObject("getTransformed"); + mailSenderService = generateMockObject("sendMail"); + userRepository = generateMockObject( + "findUserByEmailAddress", + "isUserBlacklisted", ); - configService = generateMockObject('getTransformed'); - mailSenderService = generateMockObject('sendMail'); - userRepository = generateMockObject('findUserByEmailAddress', 'isUserBlacklisted'); storage = generateMockObject( - 'uploadFile', - 'getFilePublicUrl', - 'downloadFile', - 'configureFileAccess', + "uploadFile", + "getFilePublicUrl", + "downloadFile", + "configureFileAccess", ); projectService = new ProjectService( logger, @@ -73,8 +76,8 @@ describe('ProjectService', () => { ); }); - describe('create', () => { - it('project should be create successfully', async () => { + describe("create", () => { + it("project should be create successfully", async () => { // arrange projectRepository.createProject.mockResolvedValue(1); projectRepository.getProjectBySlug.mockResolvedValue(null); @@ -83,8 +86,8 @@ describe('ProjectService', () => { // act const result = await projectService.create( { - name: 'test', - slug: 'test', + name: "test", + slug: "test", }, { isAuthenticated: true, @@ -96,12 +99,12 @@ describe('ProjectService', () => { // asset expect(result).toStrictEqual({ id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", }); }); - it('project should be not be created successfully if database crete fails', async () => { + it("project should be not be created successfully if database crete fails", async () => { // arrange projectRepository.createProject.mockResolvedValue(0); projectRepository.getProjectBySlug.mockResolvedValue(null); @@ -111,8 +114,8 @@ describe('ProjectService', () => { try { await projectService.create( { - name: 'test', - slug: 'test', + name: "test", + slug: "test", }, { isAuthenticated: true, @@ -125,22 +128,22 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'Unable to create project, please contact the admin', + "Unable to create project, please contact the admin", ); } }); }); - describe('list', () => { - it('project list should return the list of projects if user is authenticated and has required permission', async () => { + describe("list", () => { + it("project list should return the list of projects if user is authenticated and has required permission", async () => { // arrange projectRepository.getProjectsByUserId.mockResolvedValue([ { dateCreated: new Date(2025, 2, 24), id: 1, - name: 'test', - permissions: ['canCreate'], - slug: 'test', + name: "test", + permissions: ["canCreate"], + slug: "test", }, ]); @@ -156,14 +159,14 @@ describe('ProjectService', () => { { dateCreated: new Date(2025, 2, 24), id: 1, - name: 'test', - permissions: ['canCreate'], - slug: 'test', + name: "test", + permissions: ["canCreate"], + slug: "test", }, ]); }); - it('project list should not return the list of projects if user is unauthenticated', async () => { + it("project list should not return the list of projects if user is unauthenticated", async () => { // arrange projectRepository.getProjectsByUserId.mockResolvedValue([]); @@ -177,21 +180,21 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'Unable to get projects, please contact the admin', + "Unable to get projects, please contact the admin", ); } }); }); - describe('get', () => { - it('get project should return project if project exist', async () => { + describe("get", () => { + it("get project should return project if project exist", async () => { // arrange projectRepository.getProjectDetails.mockResolvedValue({ dateCreated: new Date(2024, 2, 24), id: 1, users: [], - name: 'test', - slug: 'test', + name: "test", + slug: "test", activeSubscription: null, }); projectRepository.checkUserPermission.mockResolvedValue(true); @@ -208,20 +211,20 @@ describe('ProjectService', () => { dateCreated: new Date(2024, 2, 24), id: 1, users: [], - name: 'test', - slug: 'test', + name: "test", + slug: "test", activeSubscription: null, }); }); - it('get project should return project and delete user if use dose not have permission if project exist', async () => { + it("get project should return project and delete user if use dose not have permission if project exist", async () => { // arrange projectRepository.getProjectDetails.mockResolvedValue({ dateCreated: new Date(2024, 2, 24), id: 1, users: [], - name: 'test', - slug: 'test', + name: "test", + slug: "test", activeSubscription: null, }); projectRepository.checkUserPermission.mockResolvedValue(false); @@ -237,13 +240,13 @@ describe('ProjectService', () => { expect(result).toStrictEqual({ dateCreated: new Date(2024, 2, 24), id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", activeSubscription: null, }); }); - it('get project should not return project if project dose exist', async () => { + it("get project should not return project if project dose exist", async () => { // arrange projectRepository.getProjectDetails.mockResolvedValue(null); projectRepository.checkUserPermission.mockResolvedValue(false); @@ -259,14 +262,14 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'Unable to get project, please contact the admin', + "Unable to get project, please contact the admin", ); } }); }); - describe('delete', () => { - it('delete project should be successful if project exit', async () => { + describe("delete", () => { + it("delete project should be successful if project exit", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.deleteProject.mockResolvedValue(); @@ -282,7 +285,7 @@ describe('ProjectService', () => { expect(result).toBe(true); }); - it('delete project should throw if user dose not have permission', async () => { + it("delete project should throw if user dose not have permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); projectRepository.deleteProject.mockResolvedValue(); @@ -298,14 +301,14 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You must be a project admin to delete a project', + "You must be a project admin to delete a project", ); } }); }); - describe('generate api key', () => { - it('generate api key should be successful if user has permission and details are valid', async () => { + describe("generate api key", () => { + it("generate api key should be successful if user has permission and details are valid", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.generateApiKey.mockResolvedValue(); @@ -323,7 +326,7 @@ describe('ProjectService', () => { { expireAt: newDate, force: true, - name: 'test', + name: "test", }, ); @@ -334,7 +337,7 @@ describe('ProjectService', () => { }); }); - it('generate api key should be unsuccessful if user dose not have permission and details are valid', async () => { + it("generate api key should be unsuccessful if user dose not have permission and details are valid", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); projectRepository.generateApiKey.mockResolvedValue(); @@ -353,19 +356,19 @@ describe('ProjectService', () => { { expireAt: newDate, force: true, - name: 'test', + name: "test", }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to manage API keys on this project', + "You are not authorized to manage API keys on this project", ); } }); - it('generate api key should be unsuccessful if api-key dat is in valid', async () => { + it("generate api key should be unsuccessful if api-key dat is in valid", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.generateApiKey.mockResolvedValue(); @@ -384,21 +387,21 @@ describe('ProjectService', () => { { expireAt: newDate, force: true, - name: 'test', + name: "test", }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'An expiry date in the future must be set for the API key', + "An expiry date in the future must be set for the API key", ); } }); }); - describe('revokeApiKey', () => { - it('api key should be revoke if the details are valid and user has permission', async () => { + describe("revokeApiKey", () => { + it("api key should be revoke if the details are valid and user has permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.revokeAPIKey.mockResolvedValue(undefined); @@ -413,7 +416,7 @@ describe('ProjectService', () => { expect(result).toBeUndefined(); }); - it('api key should not be revoke if the details are valid and user has no permission', async () => { + it("api key should not be revoke if the details are valid and user has no permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.revokeAPIKey.mockResolvedValue(undefined); @@ -428,29 +431,29 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to manage API keys on this project', + "You are not authorized to manage API keys on this project", ); } }); }); - describe('create collection', () => { - it('collection should be created if details are valid and user has required permission', async () => { + describe("create collection", () => { + it("collection should be created if details are valid and user has required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.getProjectById.mockResolvedValue({ dateCreated: new Date(), id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", }); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -468,11 +471,11 @@ describe('ProjectService', () => { }, { collectionData: { - id: 'test-id', - test: 'test', + id: "test-id", + test: "test", }, isPublic: false, - name: 'test', + name: "test", }, ); @@ -482,22 +485,22 @@ describe('ProjectService', () => { }); }); - it('collection should not be created if details are valid and user has no required permission', async () => { + it("collection should not be created if details are valid and user has no required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); projectRepository.getProjectById.mockResolvedValue({ dateCreated: new Date(), id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", }); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -516,11 +519,11 @@ describe('ProjectService', () => { }, { collectionData: { - id: 'test-id', - test: 'test', + id: "test-id", + test: "test", }, isPublic: false, - name: 'test', + name: "test", }, ); } catch (error) { @@ -528,22 +531,22 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to manage collections on this project', + "You are not authorized to manage collections on this project", ); } }); - it('collection should not be created if project not found', async () => { + it("collection should not be created if project not found", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); projectRepository.getProjectById.mockResolvedValue(null); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -562,41 +565,41 @@ describe('ProjectService', () => { }, { collectionData: { - id: 'test-id', - test: 'test', + id: "test-id", + test: "test", }, isPublic: false, - name: 'test', + name: "test", }, ); } catch (error) { // asset expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('Project not found'); + expect(appError.userFriendlyMessage).toBe("Project not found"); } }); }); - describe('list collection', () => { - it('list collection should return the list of collections if the user has required permissions', async () => { + describe("list collection", () => { + it("list collection should return the list of collections if the user has required permissions", async () => { // arrange const collections = [ { dateLastUpdated: new Date(), id: 1, isPublic: true, - name: 'test', + name: "test", projectId: 1, - uri: 'https://test.bonadocs.com', + uri: "https://test.bonadocs.com", }, { dateLastUpdated: new Date(), id: 2, isPublic: true, - name: 'test-2', + name: "test-2", projectId: 1, - uri: 'https://test-2.bonadocs.com', + uri: "https://test-2.bonadocs.com", }, ]; projectRepository.checkUserPermission.mockResolvedValue(true); @@ -612,7 +615,7 @@ describe('ProjectService', () => { expect(result).toEqual(collections); }); - it('list collection should return the not list if the user dose not have permission', async () => { + it("list collection should return the not list if the user dose not have permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); projectRepository.getProjectCollections.mockResolvedValue([]); @@ -627,37 +630,37 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to view project collections', + "You are not authorized to view project collections", ); } }); }); - describe('get collection', () => { - it('get project collection should be successful if id is valid and user has required permission', async () => { + describe("get collection", () => { + it("get project collection should be successful if id is valid and user has required permission", async () => { // arrange projectRepository.getProjectCollectionById.mockResolvedValue({ dateLastUpdated: new Date(2024, 2, 25), id: 1, isPublic: true, - name: 'test', + name: "test", projectId: 1, - uri: 'https://test.bonadocs.com', + uri: "https://test.bonadocs.com", }); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } return null as T; }); projectRepository.checkUserPermission.mockResolvedValue(true); - storage.getFilePublicUrl.mockReturnValue('https://test.bonadocs.com'); + storage.getFilePublicUrl.mockReturnValue("https://test.bonadocs.com"); // act const result = await projectService.getCollection(1, 1, { @@ -670,29 +673,29 @@ describe('ProjectService', () => { dateLastUpdated: new Date(2024, 2, 25), id: 1, isPublic: true, - name: 'test', + name: "test", projectId: 1, - uri: 'https://test.bonadocs.com', + uri: "https://test.bonadocs.com", }); }); - it('get project collection should not be successful if id is not valid and user has required permission', async () => { + it("get project collection should not be successful if id is not valid and user has required permission", async () => { // arrange projectRepository.getProjectCollectionById.mockResolvedValue(null); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } return null as T; }); projectRepository.checkUserPermission.mockResolvedValue(true); - storage.getFilePublicUrl.mockReturnValue('https://test.bonadocs.com'); + storage.getFilePublicUrl.mockReturnValue("https://test.bonadocs.com"); // act & asset try { @@ -703,26 +706,26 @@ describe('ProjectService', () => { } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('Collection not found'); + expect(appError.userFriendlyMessage).toBe("Collection not found"); } }); - it('get project collection should not be successful if user has no required permission', async () => { + it("get project collection should not be successful if user has no required permission", async () => { // arrange projectRepository.getProjectCollectionById.mockResolvedValue(null); configService.getTransformed.mockImplementation( (key: string): GCPConfigurations => { - if (key === 'gcp') { + if (key === "gcp") { return { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", } as GCPConfigurations; } return null as GCPConfigurations; }, ); projectRepository.checkUserPermission.mockResolvedValue(false); - storage.getFilePublicUrl.mockReturnValue('https://test.bonadocs.com'); + storage.getFilePublicUrl.mockReturnValue("https://test.bonadocs.com"); // act & asset try { @@ -734,30 +737,30 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to manage collections on this project', + "You are not authorized to manage collections on this project", ); } }); }); - describe('get collection data', () => { - it('get collection data should return data if id is valid and user has required permission', async () => { + describe("get collection data", () => { + it("get collection data should return data if id is valid and user has required permission", async () => { // arrange projectRepository.getProjectCollectionById.mockResolvedValue({ dateLastUpdated: new Date(2024, 2, 25), id: 1, isPublic: true, - name: 'test', + name: "test", projectId: 1, - uri: 'https://test.bonadocs.com', + uri: "https://test.bonadocs.com", }); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -773,16 +776,16 @@ describe('ProjectService', () => { expect(result).toStrictEqual(JSON.parse('{"test":"testing"}')); }); - it('get project collection data should not be successful if id is not valid and user has required permission', async () => { + it("get project collection data should not be successful if id is not valid and user has required permission", async () => { // arrange projectRepository.getProjectCollectionById.mockResolvedValue(null); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -799,27 +802,27 @@ describe('ProjectService', () => { } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('Collection not found'); + expect(appError.userFriendlyMessage).toBe("Collection not found"); } }); - it('get project collection data should not be successful if data can not be loaded', async () => { + it("get project collection data should not be successful if data can not be loaded", async () => { // arrange projectRepository.getProjectCollectionById.mockResolvedValue({ dateLastUpdated: new Date(2024, 2, 25), id: 1, isPublic: true, - name: 'test', + name: "test", projectId: 1, - uri: 'https://test.bonadocs.com', + uri: "https://test.bonadocs.com", }); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -837,36 +840,38 @@ describe('ProjectService', () => { } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('Collection data could not be loaded'); + expect(appError.userFriendlyMessage).toBe( + "Collection data could not be loaded", + ); } }); }); - describe('update collection', () => { - it('update collection should be successfully if user has required permission', async () => { + describe("update collection", () => { + it("update collection should be successfully if user has required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.getProjectById.mockResolvedValue({ dateCreated: new Date(2024, 2, 25), id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", }); projectRepository.getProjectCollectionById.mockResolvedValue({ dateLastUpdated: new Date(2024, 2, 25), id: 1, isPublic: true, - name: 'test', + name: "test", projectId: 1, - uri: 'https://https://test.bonadoc.com', + uri: "https://https://test.bonadoc.com", }); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -885,7 +890,7 @@ describe('ProjectService', () => { }, { isPublic: false, - name: 'test-updated', + name: "test-updated", }, ); @@ -893,7 +898,7 @@ describe('ProjectService', () => { expect(result).toBeUndefined(); }); - it('update collection should be unsuccessfully if user has no required permission', async () => { + it("update collection should be unsuccessfully if user has no required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); @@ -908,19 +913,19 @@ describe('ProjectService', () => { }, { isPublic: false, - name: 'test-updated', + name: "test-updated", }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to update a collection in this project', + "You are not authorized to update a collection in this project", ); } }); - it('update collection should be unsuccessfully if project is not found', async () => { + it("update collection should be unsuccessfully if project is not found", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.getProjectById.mockResolvedValue(null); @@ -936,24 +941,24 @@ describe('ProjectService', () => { }, { isPublic: false, - name: 'test-updated', + name: "test-updated", }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('Project not found'); + expect(appError.userFriendlyMessage).toBe("Project not found"); } }); - it('update collection should be unsuccessfully if collection is not found', async () => { + it("update collection should be unsuccessfully if collection is not found", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.getProjectById.mockResolvedValue({ dateCreated: new Date(2024, 2, 25), id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", }); projectRepository.getProjectCollectionById.mockResolvedValue(null); @@ -968,31 +973,31 @@ describe('ProjectService', () => { }, { isPublic: false, - name: 'test-updated', + name: "test-updated", }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('Collection not found'); + expect(appError.userFriendlyMessage).toBe("Collection not found"); } }); }); - describe('register device', () => { - it('register device should be successful if the details are valid and user has required permission', async () => { + describe("register device", () => { + it("register device should be successful if the details are valid and user has required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); storage.downloadFile.mockResolvedValue('{"test":"testing"}'); - storage.getFilePublicUrl.mockReturnValue('https://test.bonadocs.com'); + storage.getFilePublicUrl.mockReturnValue("https://test.bonadocs.com"); storage.uploadFile.mockResolvedValue(); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -1009,7 +1014,7 @@ describe('ProjectService', () => { }, { timestamp: 12345, - token: 'test-token', + token: "test-token", }, ); @@ -1017,19 +1022,19 @@ describe('ProjectService', () => { expect(result).toBe(true); }); - it('register device should be not successful if user has no required permission', async () => { + it("register device should be not successful if user has no required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); storage.downloadFile.mockResolvedValue('{"test":"testing"}'); - storage.getFilePublicUrl.mockReturnValue('https://test.bonadocs.com'); + storage.getFilePublicUrl.mockReturnValue("https://test.bonadocs.com"); storage.uploadFile.mockResolvedValue(); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -1047,31 +1052,31 @@ describe('ProjectService', () => { }, { timestamp: 12345, - token: 'test-token', + token: "test-token", }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to read a collection in this project', + "You are not authorized to read a collection in this project", ); } }); - it('register device should be not successful if device token already exist', async () => { + it("register device should be not successful if device token already exist", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); storage.downloadFile.mockResolvedValue('{"test-token":"test-token"}'); - storage.getFilePublicUrl.mockReturnValue('https://test.bonadocs.com'); + storage.getFilePublicUrl.mockReturnValue("https://test.bonadocs.com"); storage.uploadFile.mockResolvedValue(); configService.getTransformed.mockImplementation((key: string): T => { - if (key === 'gcp') { + if (key === "gcp") { return { gcp: { - projectId: 'bonadocs', - gcpCollectionBucket: 'bonadocs-test', - abisBucket: 'bonadocs-test', + projectId: "bonadocs", + gcpCollectionBucket: "bonadocs-test", + abisBucket: "bonadocs-test", }, } as T; } @@ -1088,7 +1093,7 @@ describe('ProjectService', () => { }, { timestamp: 12345, - token: 'test-token', + token: "test-token", }, ); @@ -1097,18 +1102,18 @@ describe('ProjectService', () => { }); }); - describe('invite user', () => { - it('invite user should be successful if user information is valid', async () => { + describe("invite user", () => { + it("invite user should be successful if user information is valid", async () => { // arrange projectRepository.inviteUserToProject.mockResolvedValue(); projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.getProjectById.mockResolvedValue({ dateCreated: new Date(2024, 24, 2), id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", }); - authService.generateJWT.mockReturnValue('test.bonadocs.token'); + authService.generateJWT.mockReturnValue("test.bonadocs.token"); mailSenderService.sendMail.mockResolvedValue(); // act @@ -1120,9 +1125,9 @@ describe('ProjectService', () => { projectId: 1, }, { - emailAddress: 'test@test.com', - memberName: 'test', - permissions: ['admin'], + emailAddress: "test@test.com", + memberName: "test", + permissions: ["admin"], }, ); @@ -1131,17 +1136,17 @@ describe('ProjectService', () => { expect(mailSenderService.sendMail).toHaveBeenCalled(); }); - it('invite user should be unsuccessful if user dose not have required permission', async () => { + it("invite user should be unsuccessful if user dose not have required permission", async () => { // arrange projectRepository.inviteUserToProject.mockResolvedValue(); projectRepository.checkUserPermission.mockResolvedValue(false); projectRepository.getProjectById.mockResolvedValue({ dateCreated: new Date(2024, 24, 2), id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", }); - authService.generateJWT.mockReturnValue('test.bonadocs.token'); + authService.generateJWT.mockReturnValue("test.bonadocs.token"); mailSenderService.sendMail.mockResolvedValue(); // act @@ -1154,31 +1159,31 @@ describe('ProjectService', () => { projectId: 1, }, { - emailAddress: 'test@test.com', - memberName: 'test', - permissions: ['admin'], + emailAddress: "test@test.com", + memberName: "test", + permissions: ["admin"], }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to invite members to this project', + "You are not authorized to invite members to this project", ); } }); - it('invite user should be unsuccessful if project is not found', async () => { + it("invite user should be unsuccessful if project is not found", async () => { // arrange projectRepository.inviteUserToProject.mockResolvedValue(); projectRepository.checkUserPermission.mockResolvedValue(false); projectRepository.getProjectById.mockResolvedValue({ dateCreated: new Date(2024, 24, 2), id: 1, - name: 'test', - slug: 'test', + name: "test", + slug: "test", }); - authService.generateJWT.mockReturnValue('test.bonadocs.token'); + authService.generateJWT.mockReturnValue("test.bonadocs.token"); mailSenderService.sendMail.mockResolvedValue(); // act @@ -1191,44 +1196,44 @@ describe('ProjectService', () => { projectId: 1, }, { - emailAddress: 'test@test.com', - memberName: 'test', - permissions: ['admin'], + emailAddress: "test@test.com", + memberName: "test", + permissions: ["admin"], }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to invite members to this project', + "You are not authorized to invite members to this project", ); } }); }); - describe('accept invite', () => { - it('accept invite should be successful if details a correct and valid you get me?', async () => { + describe("accept invite", () => { + it("accept invite should be successful if details a correct and valid you get me?", async () => { // arrange authService.validateJWT.mockReturnValue({ projectId: 1, - projectName: 'test', - userName: 'test-user', - emailAddress: 'test@test.com', + projectName: "test", + userName: "test-user", + emailAddress: "test@test.com", }); projectRepository.acceptProjectInvitation.mockResolvedValue(); userRepository.findUserByEmailAddress.mockResolvedValue({ - emailAddress: 'test@test.com', - firstName: 'test-firetname', - lastName: 'test-lastname', + emailAddress: "test@test.com", + firstName: "test-firetname", + lastName: "test-lastname", id: 1, - username: 'test', + username: "test", }); projectRepository.getInvitationPermissionFlags.mockResolvedValue(1); projectRepository.addUserToProject.mockResolvedValue(); mailSenderService.sendMail.mockResolvedValue(); // act - const result = await projectService.acceptInvite('test-token', { + const result = await projectService.acceptInvite("test-token", { isAuthenticated: true, userId: 1, }); @@ -1238,16 +1243,16 @@ describe('ProjectService', () => { expect(mailSenderService.sendMail).toHaveBeenCalled(); }); - it('accept invite should be unsuccessful if unable to validate token ', async () => { + it("accept invite should be unsuccessful if unable to validate token ", async () => { // arrange authService.validateJWT.mockReturnValue(false); projectRepository.acceptProjectInvitation.mockResolvedValue(); userRepository.findUserByEmailAddress.mockResolvedValue({ - emailAddress: 'test@test.com', - firstName: 'test-firetname', - lastName: 'test-lastname', + emailAddress: "test@test.com", + firstName: "test-firetname", + lastName: "test-lastname", id: 1, - username: 'test', + username: "test", }); projectRepository.getInvitationPermissionFlags.mockResolvedValue(1); projectRepository.addUserToProject.mockResolvedValue(); @@ -1255,24 +1260,26 @@ describe('ProjectService', () => { // act & assert try { - await projectService.acceptInvite('test-token', { + await projectService.acceptInvite("test-token", { isAuthenticated: true, userId: 1, }); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('Invalid project invitation JWT'); + expect(appError.userFriendlyMessage).toBe( + "Invalid project invitation JWT", + ); } }); - it('accept invite should be unsuccessful if user not found', async () => { + it("accept invite should be unsuccessful if user not found", async () => { // arrange authService.validateJWT.mockReturnValue({ projectId: 1, - projectName: 'test', - userName: 'test-user', - emailAddress: 'test@test.com', + projectName: "test", + userName: "test-user", + emailAddress: "test@test.com", }); projectRepository.acceptProjectInvitation.mockResolvedValue(); userRepository.findUserByEmailAddress.mockResolvedValue(null); @@ -1282,7 +1289,7 @@ describe('ProjectService', () => { // act & assert try { - await projectService.acceptInvite('test-token', { + await projectService.acceptInvite("test-token", { isAuthenticated: true, userId: 1, }); @@ -1290,14 +1297,14 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'User not found, possibly registration not yet complete', + "User not found, possibly registration not yet complete", ); } }); }); - describe('cancel invitation', () => { - it('cancel invitation should be successful if details are valid', async () => { + describe("cancel invitation", () => { + it("cancel invitation should be successful if details are valid", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); @@ -1309,7 +1316,7 @@ describe('ProjectService', () => { userId: 1, }, { - emailAddress: 'test@tes.com', + emailAddress: "test@tes.com", }, ); @@ -1317,7 +1324,7 @@ describe('ProjectService', () => { expect(result).toBeUndefined(); }); - it('cancel invitation should be unsuccessful if user has no required permission', async () => { + it("cancel invitation should be unsuccessful if user has no required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); @@ -1330,21 +1337,21 @@ describe('ProjectService', () => { userId: 1, }, { - emailAddress: 'test@tes.com', + emailAddress: "test@tes.com", }, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to manage collections on this project', + "You are not authorized to manage collections on this project", ); } }); }); - describe('list invitations', () => { - it('list invitation should return invitation list is user has required permission', async () => { + describe("list invitations", () => { + it("list invitation should return invitation list is user has required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.listProjectInvitations.mockResolvedValue({ @@ -1353,8 +1360,8 @@ describe('ProjectService', () => { { accepted: true, dateCreated: new Date(2024, 2, 24), - emailAddress: 'test@test.com', - permissions: ['admin'], + emailAddress: "test@test.com", + permissions: ["admin"], }, ], }); @@ -1372,14 +1379,14 @@ describe('ProjectService', () => { { accepted: true, dateCreated: new Date(2024, 2, 24), - emailAddress: 'test@test.com', - permissions: ['admin'], + emailAddress: "test@test.com", + permissions: ["admin"], }, ], }); }); - it('list invitation should not return invitation list is user has no required permission', async () => { + it("list invitation should not return invitation list is user has no required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); projectRepository.listProjectInvitations.mockResolvedValue({ @@ -1388,8 +1395,8 @@ describe('ProjectService', () => { { accepted: true, dateCreated: new Date(2024, 2, 24), - emailAddress: 'test@test.com', - permissions: ['admin'], + emailAddress: "test@test.com", + permissions: ["admin"], }, ], }); @@ -1404,26 +1411,26 @@ describe('ProjectService', () => { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to view project invitations', + "You are not authorized to view project invitations", ); } }); }); - describe('list users', () => { - it('list users should be successful if user has required permission', async () => { + describe("list users", () => { + it("list users should be successful if user has required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.getProjectUsers.mockResolvedValue({ id: 1, users: [ { - emailAddress: 'test@test.com', - firstName: 'test', + emailAddress: "test@test.com", + firstName: "test", id: 1, - lastName: 'test', - permissions: ['admin'], - username: 'test', + lastName: "test", + permissions: ["admin"], + username: "test", }, ], }); @@ -1438,30 +1445,30 @@ describe('ProjectService', () => { expect(result).toStrictEqual({ users: [ { - emailAddress: 'test@test.com', - firstName: 'test', + emailAddress: "test@test.com", + firstName: "test", id: 1, - lastName: 'test', - permissions: ['admin'], - username: 'test', + lastName: "test", + permissions: ["admin"], + username: "test", }, ], }); }); - it('list users should be unsuccessful if user has no required permission', async () => { + it("list users should be unsuccessful if user has no required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.getProjectUsers.mockResolvedValue({ id: 1, users: [ { - emailAddress: 'test@test.com', - firstName: 'test', + emailAddress: "test@test.com", + firstName: "test", id: 1, - lastName: 'test', - permissions: ['admin'], - username: 'test', + lastName: "test", + permissions: ["admin"], + username: "test", }, ], }); @@ -1475,11 +1482,13 @@ describe('ProjectService', () => { } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('You are not authorized to view users project'); + expect(appError.userFriendlyMessage).toBe( + "You are not authorized to view users project", + ); } }); - it('list users should be unsuccessful if project not found', async () => { + it("list users should be unsuccessful if project not found", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); projectRepository.getProjectUsers.mockResolvedValue(undefined); @@ -1493,13 +1502,13 @@ describe('ProjectService', () => { } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('Project not found'); + expect(appError.userFriendlyMessage).toBe("Project not found"); } }); }); - describe('remove user', () => { - it('remove user should be successful if user has required permission', async () => { + describe("remove user", () => { + it("remove user should be successful if user has required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(true); @@ -1513,7 +1522,7 @@ describe('ProjectService', () => { expect(result).toBeUndefined(); }); - it('remove user should be unsuccessful if user has no required permission', async () => { + it("remove user should be unsuccessful if user has no required permission", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValue(false); @@ -1526,16 +1535,18 @@ describe('ProjectService', () => { } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('You are not authorized to manage users'); + expect(appError.userFriendlyMessage).toBe( + "You are not authorized to manage users", + ); } }); }); - describe('update user permission', () => { + describe("update user permission", () => { const projectId = 1; const targetUserId = 2; const authData = { isAuthenticated: true, userId: 1, projectId: 1 }; - it('should update permissions successfully when no admin privilege is requested', async () => { + it("should update permissions successfully when no admin privilege is requested", async () => { // arrange projectRepository.checkUserPermission.mockResolvedValueOnce(true); // for 'writeUsers' @@ -1543,19 +1554,17 @@ describe('ProjectService', () => { await projectService.updateUserPermission( projectId, targetUserId, - { permissions: ['readUsers'] }, + { permissions: ["readUsers"] }, authData, ); // assert - expect(projectRepository.updateProjectUserPermissions).toHaveBeenCalledWith( - projectId, - targetUserId, - ['readUsers'], - ); + expect( + projectRepository.updateProjectUserPermissions, + ).toHaveBeenCalledWith(projectId, targetUserId, ["readUsers"]); }); - it('should update permissions successfully when admin privilege is requested and user has admin permission', async () => { + it("should update permissions successfully when admin privilege is requested and user has admin permission", async () => { // arrange projectRepository.checkUserPermission .mockResolvedValueOnce(true) // for 'writeUsers' @@ -1565,19 +1574,17 @@ describe('ProjectService', () => { await projectService.updateUserPermission( projectId, targetUserId, - { permissions: ['admin'] }, + { permissions: ["admin"] }, authData, ); // assert - expect(projectRepository.updateProjectUserPermissions).toHaveBeenCalledWith( - projectId, - targetUserId, - ['admin'], - ); + expect( + projectRepository.updateProjectUserPermissions, + ).toHaveBeenCalledWith(projectId, targetUserId, ["admin"]); }); - it('should fail if user do not have required permission write', async () => { + it("should fail if user do not have required permission write", async () => { projectRepository.checkUserPermission .mockResolvedValueOnce(false) // for 'writeUsers' .mockResolvedValueOnce(true); @@ -1586,17 +1593,19 @@ describe('ProjectService', () => { await projectService.updateUserPermission( projectId, targetUserId, - { permissions: ['writeUsers'] }, + { permissions: ["writeUsers"] }, authData, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; - expect(appError.userFriendlyMessage).toBe('You are not authorized to manage users'); + expect(appError.userFriendlyMessage).toBe( + "You are not authorized to manage users", + ); } }); - it('should fail if user do not have required permission admin', async () => { + it("should fail if user do not have required permission admin", async () => { projectRepository.checkUserPermission .mockResolvedValueOnce(true) // for 'writeUsers' .mockResolvedValueOnce(false); @@ -1605,14 +1614,14 @@ describe('ProjectService', () => { await projectService.updateUserPermission( projectId, targetUserId, - { permissions: ['admin'] }, + { permissions: ["admin"] }, authData, ); } catch (error) { expect(error).toBeInstanceOf(ApplicationError); const appError = error as ApplicationError; expect(appError.userFriendlyMessage).toBe( - 'You are not authorized to update this user to admin', + "You are not authorized to update this user to admin", ); } }); diff --git a/src/modules/project/project.service.ts b/src/modules/project/project.service.ts index 921e26c..cb14a68 100644 --- a/src/modules/project/project.service.ts +++ b/src/modules/project/project.service.ts @@ -1,20 +1,23 @@ -import { createHash, randomBytes } from 'crypto'; +import { createHash, randomBytes } from "crypto"; -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { AuthService } from '../auth/auth.service'; -import { FirebaseJWTProvider } from '../auth/firebase/index'; -import { GCP } from '../configuration/config.interface'; -import { ConfigService } from '../configuration/config.service'; -import { ApplicationError, applicationErrorCodes } from '../errors/ApplicationError'; -import { MailgunSender } from '../mailing/mailgun'; -import { MailTemplate } from '../mailing/types'; -import { ProjectRepository } from '../repositories/projects/project.repository'; -import { UserRepository } from '../repositories/users/user.repository'; -import { Storage } from '../storage'; +import { AuthService } from "../auth/auth.service"; +import { FirebaseJWTProvider } from "../auth/firebase/index"; +import { GCP } from "../configuration/config.interface"; +import { ConfigService } from "../configuration/config.service"; +import { + ApplicationError, + applicationErrorCodes, +} from "../errors/ApplicationError"; +import { MailgunSender } from "../mailing/mailgun"; +import { MailTemplate } from "../mailing/types"; +import { ProjectRepository } from "../repositories/projects/project.repository"; +import { UserRepository } from "../repositories/users/user.repository"; +import { Storage } from "../storage"; import { CancelProjectInvitationRequest, @@ -36,7 +39,7 @@ import { RegisterDeviceRequest, UpdateProjectCollectionRequest, UpdateProjectUserPermissionRequest, -} from './project.interface'; +} from "./project.interface"; @Service() export class ProjectService { @@ -50,27 +53,34 @@ export class ProjectService { @Inject() private configService: ConfigService, ) {} - async create(request: CreateProjectRequest, authData: AuthData): Promise { + async create( + request: CreateProjectRequest, + authData: AuthData, + ): Promise { // check if the project slug already exists - const projectExists = await this.projectRepository.getProjectBySlug(request.slug); + const projectExists = await this.projectRepository.getProjectBySlug( + request.slug, + ); if (projectExists) { throw new ApplicationError({ logger: this.logger, - message: 'Project already exists with slug provided', + message: "Project already exists with slug provided", errorCode: applicationErrorCodes.invalidRequest, - userFriendlyMessage: 'Project already exists with slug provided', + userFriendlyMessage: "Project already exists with slug provided", }); } // check if user is not yet blacklisted - const userIsBlacklisted = await this.userRepository.isUserBlacklisted(authData.userId!); + const userIsBlacklisted = await this.userRepository.isUserBlacklisted( + authData.userId!, + ); if (userIsBlacklisted) { throw new ApplicationError({ logger: this.logger, - message: 'User is blacklisted', + message: "User is blacklisted", errorCode: applicationErrorCodes.unauthorized, statusCode: 403, - userFriendlyMessage: 'You are not authorized to create a project', + userFriendlyMessage: "You are not authorized to create a project", }); } @@ -83,9 +93,10 @@ export class ProjectService { if (!projectId) { throw new ApplicationError({ logger: this.logger, - message: 'Failed to create project', + message: "Failed to create project", errorCode: applicationErrorCodes.invalidRequest, - userFriendlyMessage: 'Unable to create project, please contact the admin', + userFriendlyMessage: + "Unable to create project, please contact the admin", }); } return { @@ -96,35 +107,41 @@ export class ProjectService { } async list(authData: AuthData): Promise { - const projects = await this.projectRepository.getProjectsByUserId(authData.userId!); + const projects = await this.projectRepository.getProjectsByUserId( + authData.userId!, + ); if (!projects) { throw new ApplicationError({ logger: this.logger, - message: 'Failed get projects', + message: "Failed get projects", errorCode: applicationErrorCodes.invalidRequest, - userFriendlyMessage: 'Unable to get projects, please contact the admin', + userFriendlyMessage: "Unable to get projects, please contact the admin", }); } return projects; } - async get(projectId: number, authData: AuthData): Promise { - const projectDetails = await this.projectRepository.getProjectDetails(projectId); + async get( + projectId: number, + authData: AuthData, + ): Promise { + const projectDetails = + await this.projectRepository.getProjectDetails(projectId); if (!projectDetails) { throw new ApplicationError({ logger: this.logger, - message: 'Failed get project', + message: "Failed get project", errorCode: applicationErrorCodes.invalidRequest, - userFriendlyMessage: 'Unable to get project, please contact the admin', + userFriendlyMessage: "Unable to get project, please contact the admin", }); } const canReadUsers = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'readUsers', + "readUsers", ); if (!canReadUsers) { @@ -138,12 +155,12 @@ export class ProjectService { const hasPermission = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'admin', + "admin", ); if (!hasPermission) { throw new ApplicationError({ - message: 'You must be a project admin to delete a project', + message: "You must be a project admin to delete a project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -162,7 +179,7 @@ export class ProjectService { ): Promise { if (request.expireAt.getTime() < Date.now()) { throw new ApplicationError({ - message: 'An expiry date in the future must be set for the API key', + message: "An expiry date in the future must be set for the API key", errorCode: applicationErrorCodes.invalidRequest, logger: this.logger, }); @@ -171,20 +188,20 @@ export class ProjectService { const isAdmin = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'admin', + "admin", ); if (!isAdmin) { throw new ApplicationError({ - message: 'You are not authorized to manage API keys on this project', + message: "You are not authorized to manage API keys on this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, }); } - const apiKey = `bta_${randomBytes(32).toString('hex')}`; - const apiKeyHash = createHash('sha256').update(apiKey).digest('hex'); + const apiKey = `bta_${randomBytes(32).toString("hex")}`; + const apiKeyHash = createHash("sha256").update(apiKey).digest("hex"); this.projectRepository.generateApiKey( { @@ -198,10 +215,10 @@ export class ProjectService { if (!apiKey) { throw new ApplicationError({ - message: 'Failed to generate API key', + message: "Failed to generate API key", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, - userFriendlyMessage: 'Failed to generate API key', + userFriendlyMessage: "Failed to generate API key", statusCode: 403, }); } @@ -210,16 +227,19 @@ export class ProjectService { }; } - async listApiKeys(projectId: number, authData: AuthData): Promise { + async listApiKeys( + projectId: number, + authData: AuthData, + ): Promise { const isAdmin = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'admin', + "admin", ); if (!isAdmin) { throw new ApplicationError({ - message: 'You are not authorized to manage API keys on this project', + message: "You are not authorized to manage API keys on this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -233,16 +253,20 @@ export class ProjectService { }; } - async revokeApiKey(projectId: number, apiKeyId: number, authData: AuthData): Promise { + async revokeApiKey( + projectId: number, + apiKeyId: number, + authData: AuthData, + ): Promise { const isAdmin = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'admin', + "admin", ); if (!isAdmin) { throw new ApplicationError({ - message: 'You are not authorized to manage API keys on this project', + message: "You are not authorized to manage API keys on this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -260,12 +284,12 @@ export class ProjectService { const canWriteCollections = this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'writeCollections', + "writeCollections", ); if (!canWriteCollections) { throw new ApplicationError({ - message: 'You are not authorized to manage collections on this project', + message: "You are not authorized to manage collections on this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, }); @@ -275,13 +299,14 @@ export class ProjectService { if (!project) { throw new ApplicationError({ logger: this.logger, - message: 'Project not found', + message: "Project not found", errorCode: applicationErrorCodes.notFound, }); } const fileId = request.collectionData.id; - const bucketName = this.configService.getTransformed('gcp').collectionsBucket; + const bucketName = + this.configService.getTransformed("gcp").collectionsBucket; const fileName = `project-${projectId}/${fileId}.json`; await this.storage.uploadFile( bucketName, @@ -309,19 +334,20 @@ export class ProjectService { const canReadCollections = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'readCollections', + "readCollections", ); if (!canReadCollections) { throw new ApplicationError({ - message: 'You are not authorized to view project collections', + message: "You are not authorized to view project collections", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, }); } - const collections = await this.projectRepository.getProjectCollections(projectId); + const collections = + await this.projectRepository.getProjectCollections(projectId); return collections; } @@ -331,15 +357,16 @@ export class ProjectService { collectionId: number, authData: AuthData, ): Promise { - const canWriteCollections = await this.projectRepository.checkUserPermission( - projectId, - authData.userId!, - 'writeCollections', - ); + const canWriteCollections = + await this.projectRepository.checkUserPermission( + projectId, + authData.userId!, + "writeCollections", + ); if (!canWriteCollections) { throw new ApplicationError({ - message: 'You are not authorized to manage collections on this project', + message: "You are not authorized to manage collections on this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -353,12 +380,13 @@ export class ProjectService { if (!collection) { throw new ApplicationError({ - message: 'Collection not found', + message: "Collection not found", errorCode: applicationErrorCodes.notFound, logger: this.logger, }); } - const bucketName = this.configService.getTransformed('gcp').collectionsBucket; + const bucketName = + this.configService.getTransformed("gcp").collectionsBucket; const publicUrl = this.storage.getFilePublicUrl(bucketName, collection.uri); return { @@ -380,19 +408,23 @@ export class ProjectService { if (collection == null) { throw new ApplicationError({ - message: 'Collection not found', + message: "Collection not found", errorCode: applicationErrorCodes.notFound, logger: this.logger, }); } - const bucketName = this.configService.getTransformed('gcp').collectionsBucket; + const bucketName = + this.configService.getTransformed("gcp").collectionsBucket; const fileName = collection?.uri; - const savedCollectionData = await this.storage.downloadFile(bucketName, fileName); + const savedCollectionData = await this.storage.downloadFile( + bucketName, + fileName, + ); if (!savedCollectionData) { throw new ApplicationError({ - message: 'Collection data could not be loaded', + message: "Collection data could not be loaded", errorCode: applicationErrorCodes.notFound, logger: this.logger, }); @@ -410,30 +442,38 @@ export class ProjectService { const canReadCollections = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'readCollections', + "readCollections", ); if (!canReadCollections) { throw new ApplicationError({ - message: 'You are not authorized to read a collection in this project', + message: "You are not authorized to read a collection in this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, }); } - const bucketName = this.configService.getTransformed('gcp').collectionsBucket; + const bucketName = + this.configService.getTransformed("gcp").collectionsBucket; const group = FirebaseJWTProvider.getFcmGroupId(projectId, collectionId); const fileName = `firebase-device-ids/${group}.json`; const existingFile = await this.storage.downloadFile(bucketName, fileName); - const existingData = JSON.parse(existingFile || '{}') as Record; + const existingData = JSON.parse(existingFile || "{}") as Record< + string, + number + >; if (existingData[request.token]) { return false; } existingData[request.token] = request.timestamp; - await this.storage.uploadFile(bucketName, fileName, JSON.stringify(existingData)); + await this.storage.uploadFile( + bucketName, + fileName, + JSON.stringify(existingData), + ); return true; } @@ -446,12 +486,13 @@ export class ProjectService { const canReadCollections = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'writeCollections', + "writeCollections", ); if (!canReadCollections) { throw new ApplicationError({ - message: 'You are not authorized to update a collection in this project', + message: + "You are not authorized to update a collection in this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -462,7 +503,7 @@ export class ProjectService { if (!project) { throw new ApplicationError({ - message: 'Project not found', + message: "Project not found", errorCode: applicationErrorCodes.notFound, logger: this.logger, }); @@ -475,15 +516,20 @@ export class ProjectService { if (!collection) { throw new ApplicationError({ - message: 'Collection not found', + message: "Collection not found", errorCode: applicationErrorCodes.notFound, logger: this.logger, }); } - const bucketName = this.configService.getTransformed('gcp').collectionsBucket; + const bucketName = + this.configService.getTransformed("gcp").collectionsBucket; const fileName = collection!.uri; - await this.storage.configureFileAccess(bucketName, fileName, request.isPublic); + await this.storage.configureFileAccess( + bucketName, + fileName, + request.isPublic, + ); await this.projectRepository.updateProjectCollection( projectId, collectionId, @@ -501,12 +547,13 @@ export class ProjectService { const collectionAdmin = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'admin', + "admin", ); if (!collectionAdmin) { throw new ApplicationError({ - message: 'You are not authorized to delete a collection in this project', + message: + "You are not authorized to delete a collection in this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -517,7 +564,7 @@ export class ProjectService { if (!project) { throw new ApplicationError({ - message: 'Project not found', + message: "Project not found", errorCode: applicationErrorCodes.notFound, logger: this.logger, }); @@ -535,12 +582,12 @@ export class ProjectService { const canWriteUser = this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'writeUsers', + "writeUsers", ); if (!canWriteUser) { throw new ApplicationError({ - message: 'You are not authorized to invite members to this project', + message: "You are not authorized to invite members to this project", logger: this.logger, errorCode: applicationErrorCodes.unauthorized, statusCode: 403, @@ -550,7 +597,7 @@ export class ProjectService { const project = await this.projectRepository.getProjectById(projectId); if (!project) { throw new ApplicationError({ - message: 'Project not found', + message: "Project not found", errorCode: applicationErrorCodes.notFound, logger: this.logger, }); @@ -593,19 +640,25 @@ export class ProjectService { const tokenPayload = this.authService.validateJWT(token); if (!tokenPayload) { throw new ApplicationError({ - message: 'Invalid project invitation JWT', + message: "Invalid project invitation JWT", errorCode: applicationErrorCodes.invalidRequest, logger: this.logger, }); } - const { projectId, projectName, userName, emailAddress } = tokenPayload as TokenPayload; - await this.projectRepository.acceptProjectInvitation(projectId, emailAddress as string); + const { projectId, projectName, userName, emailAddress } = + tokenPayload as TokenPayload; + await this.projectRepository.acceptProjectInvitation( + projectId, + emailAddress as string, + ); - const invitedUser = await this.userRepository.findUserByEmailAddress(emailAddress as string); + const invitedUser = await this.userRepository.findUserByEmailAddress( + emailAddress as string, + ); if (!invitedUser) { throw new ApplicationError({ - message: 'User not found, possibly registration not yet complete', + message: "User not found, possibly registration not yet complete", errorCode: applicationErrorCodes.invalidRequest, logger: this.logger, }); @@ -613,20 +666,25 @@ export class ProjectService { if (invitedUser.id !== authData.userId) { throw new ApplicationError({ - message: 'User is not authenticated', + message: "User is not authenticated", errorCode: applicationErrorCodes.invalidRequest, logger: this.logger, }); } // add user to project - const permission = await this.projectRepository.getInvitationPermissionFlags( - emailAddress as string, + const permission = + await this.projectRepository.getInvitationPermissionFlags( + emailAddress as string, + projectId, + ); + + await this.projectRepository.addUserToProject( projectId, + authData.userId!, + permission, ); - await this.projectRepository.addUserToProject(projectId, authData.userId!, permission); - await this.mailSender .sendMail({ subject: `You have joined ${projectName} on Bonadocs`, @@ -640,7 +698,7 @@ export class ProjectService { }, }) .catch((e) => { - this.logger.error('Failed to send project joined email', e); + this.logger.error("Failed to send project joined email", e); }); } @@ -652,31 +710,37 @@ export class ProjectService { const canWriteCollections = this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'writeCollections', + "writeCollections", ); if (!canWriteCollections) { throw new ApplicationError({ - message: 'You are not authorized to manage collections on this project', + message: "You are not authorized to manage collections on this project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, }); } - await this.projectRepository.cancelProjectInvitation(projectId, request.emailAddress); + await this.projectRepository.cancelProjectInvitation( + projectId, + request.emailAddress, + ); } - async listInvites(projectId: number, authData: AuthData): Promise { + async listInvites( + projectId: number, + authData: AuthData, + ): Promise { const canReadUsers = this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'readUsers', + "readUsers", ); if (!canReadUsers) { throw new ApplicationError({ - message: 'You are not authorized to view project invitations', + message: "You are not authorized to view project invitations", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -686,16 +750,19 @@ export class ProjectService { return this.projectRepository.listProjectInvitations(projectId); } - async listUsers(projectId: number, authData: AuthData): Promise { + async listUsers( + projectId: number, + authData: AuthData, + ): Promise { const canReadUsers = await this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'readUsers', + "readUsers", ); if (!canReadUsers) { throw new ApplicationError({ - message: 'You are not authorized to view users project', + message: "You are not authorized to view users project", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -706,7 +773,7 @@ export class ProjectService { if (!data) { throw new ApplicationError({ - message: 'Project not found', + message: "Project not found", errorCode: applicationErrorCodes.notFound, logger: this.logger, }); @@ -717,16 +784,20 @@ export class ProjectService { }; } - async removeUser(projectId: number, userId: number, authData: AuthData): Promise { + async removeUser( + projectId: number, + userId: number, + authData: AuthData, + ): Promise { const canWriteUsers = this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'writeUsers', + "writeUsers", ); if (!canWriteUsers) { throw new ApplicationError({ - message: 'You are not authorized to manage users', + message: "You are not authorized to manage users", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, @@ -745,28 +816,28 @@ export class ProjectService { const canWriteUsers = this.projectRepository.checkUserPermission( projectId, authData.userId!, - 'writeUsers', + "writeUsers", ); if (!canWriteUsers) { throw new ApplicationError({ - message: 'You are not authorized to manage users', + message: "You are not authorized to manage users", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, }); } - if (request.permissions.includes('admin')) { + if (request.permissions.includes("admin")) { const isAdmin = await this.projectRepository.checkUserPermission( Number(projectId), authData.userId!, - 'admin', + "admin", ); if (!isAdmin) { throw new ApplicationError({ - message: 'You are not authorized to update this user to admin', + message: "You are not authorized to update this user to admin", errorCode: applicationErrorCodes.unauthorized, logger: this.logger, statusCode: 403, diff --git a/src/modules/repositories/billing/billing.repository.ts b/src/modules/repositories/billing/billing.repository.ts index d876328..2e9e971 100644 --- a/src/modules/repositories/billing/billing.repository.ts +++ b/src/modules/repositories/billing/billing.repository.ts @@ -1,14 +1,21 @@ -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { DbContext, withDbContext } from '../../connection/dbcontext'; -import { InvoicePaymentStatus, InvoiceStatus } from '../../shared/types/invoice'; -import { SubscriptionStatus } from '../../shared/types/subscriptions'; -import { PlanResourceDetails, PlanSummaryDto, PlanWithMetadataDto } from '../projects'; +import { DbContext, withDbContext } from "../../connection/dbcontext"; +import { + InvoicePaymentStatus, + InvoiceStatus, +} from "../../shared/types/invoice"; +import { SubscriptionStatus } from "../../shared/types/subscriptions"; +import { + PlanResourceDetails, + PlanSummaryDto, + PlanWithMetadataDto, +} from "../projects"; -import { queries } from './queries'; +import { queries } from "./queries"; import { Invoice, InvoiceItem, @@ -17,14 +24,19 @@ import { ListInvoicePayment, SetUpSubscription, Subscription, -} from './types'; +} from "./types"; @Service() export class BillingRepository { - constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} + constructor( + @Inject(diConstants.logger) private readonly logger: BonadocsLogger, + ) {} @withDbContext - async createInvoice(data: Invoice, context?: DbContext): Promise { + async createInvoice( + data: Invoice, + context?: DbContext, + ): Promise { try { context?.beginTransaction(); const createInvoiceResult = await context?.query({ @@ -39,13 +51,13 @@ export class BillingRepository { data.dueDate, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create invoice', + validationErrorMessage: "Failed to create invoice", }); context?.commitTransaction(); return createInvoiceResult?.rows[0]?.id; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to create invoice', error); + this.logger.error("Failed to create invoice", error); return undefined; } } @@ -69,57 +81,74 @@ export class BillingRepository { data.status, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create invoice payment', + validationErrorMessage: "Failed to create invoice payment", }); context?.commitTransaction(); return createInvoicePaymentResult?.rows[0]?.id; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to create invoice payment', error); + this.logger.error("Failed to create invoice payment", error); return undefined; } } @withDbContext - async createInvoiceItem(data: InvoiceItem, context?: DbContext): Promise { + async createInvoiceItem( + data: InvoiceItem, + context?: DbContext, + ): Promise { try { context?.beginTransaction(); const createInvoiceItemResult = await context?.query({ text: queries.createInvoiceItem, - values: [data.invoiceId, data.resourceId, data.quantity, data.amount, data.currency], + values: [ + data.invoiceId, + data.resourceId, + data.quantity, + data.amount, + data.currency, + ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create invoice item', + validationErrorMessage: "Failed to create invoice item", }); context?.commitTransaction(); return createInvoiceItemResult?.rows[0]?.id; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to create invoice item', error); + this.logger.error("Failed to create invoice item", error); return undefined; } } @withDbContext - async updateInvoiceStatus(id: number, status: string, context?: DbContext): Promise { + async updateInvoiceStatus( + id: number, + status: string, + context?: DbContext, + ): Promise { try { context?.beginTransaction(); const updateInvoiceResult = await context?.query({ text: queries.updateInvoiceStatus, values: [status, id], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice status', + validationErrorMessage: "Failed to update invoice status", }); context?.commitTransaction(); return !!updateInvoiceResult; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update invoice status', error); + this.logger.error("Failed to update invoice status", error); return false; } } @withDbContext - async updateInvoiceStatusBulk(ids: number[], status: string, context?: DbContext): Promise { + async updateInvoiceStatusBulk( + ids: number[], + status: string, + context?: DbContext, + ): Promise { try { context?.beginTransaction(); ids.forEach(async (id) => { @@ -127,28 +156,31 @@ export class BillingRepository { text: queries.updateInvoiceStatus, values: [status, id], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice status', + validationErrorMessage: "Failed to update invoice status", }); }); context?.commitTransaction(); } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update invoice status', error); + this.logger.error("Failed to update invoice status", error); } } @withDbContext - async getInvoiceById(id: number, context?: DbContext): Promise { + async getInvoiceById( + id: number, + context?: DbContext, + ): Promise { try { const getInvoiceResult = await context?.query({ text: queries.getInvoiceById, values: [id], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to get invoice', + validationErrorMessage: "Failed to get invoice", }); return getInvoiceResult?.rows[0]; } catch (error) { - this.logger.error('Failed to get invoice', error); + this.logger.error("Failed to get invoice", error); return undefined; } } @@ -163,11 +195,11 @@ export class BillingRepository { text: queries.getInvoiceBySubscriptionId, values: [subscriptionId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to get invoice', + validationErrorMessage: "Failed to get invoice", }); return getInvoiceResult?.rows[0]; } catch (error) { - this.logger.error('Failed to get invoice', error); + this.logger.error("Failed to get invoice", error); return undefined; } } @@ -184,13 +216,13 @@ export class BillingRepository { text: queries.updateInvoicePaymentStatus, values: [status, id], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice payment status', + validationErrorMessage: "Failed to update invoice payment status", }); context?.commitTransaction(); return !!updateInvoicePaymentResult; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update invoice payment status', error); + this.logger.error("Failed to update invoice payment status", error); return false; } } @@ -207,13 +239,13 @@ export class BillingRepository { text: queries.updateInvoicePayment, values: [data.paymentChannel, data.paymentReference, id], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice payment', + validationErrorMessage: "Failed to update invoice payment", }); context?.commitTransaction(); return !!updateInvoicePaymentResult; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update invoice payment', error); + this.logger.error("Failed to update invoice payment", error); return false; } } @@ -228,11 +260,11 @@ export class BillingRepository { text: queries.getInvoicePaymentById, values: [id], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to get invoice payment', + validationErrorMessage: "Failed to get invoice payment", }); return getInvoicePaymentResult?.rows[0]; } catch (error) { - this.logger.error('Failed to get invoice payment', error); + this.logger.error("Failed to get invoice payment", error); return undefined; } } @@ -248,7 +280,7 @@ export class BillingRepository { text: queries.getUnpaidInvoicesWithCloseDueDate, values: [startDate, endDate], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to get unpaid invoices', + validationErrorMessage: "Failed to get unpaid invoices", }); if (!getUnpaidInvoicesResult?.rowCount) { return []; @@ -265,7 +297,7 @@ export class BillingRepository { dueDate: row.due_date, })); } catch (error) { - this.logger.error('Failed to get unpaid invoices', error); + this.logger.error("Failed to get unpaid invoices", error); return []; } } @@ -280,7 +312,7 @@ export class BillingRepository { text: queries.getInvoicePaymentsByInvoiceIds, values: [invoiceIds], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to get invoice payments', + validationErrorMessage: "Failed to get invoice payments", }); if (!getInvoicePaymentsResult?.rowCount) { return []; @@ -296,7 +328,7 @@ export class BillingRepository { dateCreated: row.date_created, })); } catch (error) { - this.logger.error('Failed to get invoice payments', error); + this.logger.error("Failed to get invoice payments", error); return []; } } @@ -307,7 +339,7 @@ export class BillingRepository { const getPlansResult = await context?.query({ text: queries.getAllPlans, validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to get plans', + validationErrorMessage: "Failed to get plans", }); return ( getPlansResult?.rows?.map((row) => ({ @@ -321,26 +353,29 @@ export class BillingRepository { })) || [] ); } catch (error) { - this.logger.error('Failed to get plans', error); + this.logger.error("Failed to get plans", error); return []; } } @withDbContext - async getPlan(planId: number, context?: DbContext): Promise { + async getPlan( + planId: number, + context?: DbContext, + ): Promise { try { const getPlansResult = await context?.query({ text: queries.getPlanById, values: [planId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to get plans', + validationErrorMessage: "Failed to get plans", }); if (!getPlansResult?.rowCount) { return null; } return getPlansResult.rows[0]; } catch (error) { - this.logger.error('Failed to get plans', error); + this.logger.error("Failed to get plans", error); return null; } } @@ -365,19 +400,19 @@ export class BillingRepository { }, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update subscription metadata', + validationErrorMessage: "Failed to update subscription metadata", }); await context?.query({ text: queries.updateInvoicePaymentMetadata, values: [invoicePaymentId, metadata], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice payment metadata', + validationErrorMessage: "Failed to update invoice payment metadata", }); context?.commitTransaction(); return true; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update metadata', error); + this.logger.error("Failed to update metadata", error); return false; } } @@ -394,13 +429,13 @@ export class BillingRepository { text: queries.updateSubscriptionMetadata, values: [subscriptionId, metadata], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update subscription metadata', + validationErrorMessage: "Failed to update subscription metadata", }); context?.commitTransaction(); return true; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update metadata', error); + this.logger.error("Failed to update metadata", error); return false; } } @@ -422,41 +457,41 @@ export class BillingRepository { text: queries.updateInvoiceStatus, values: [invoiceId, invoiceStatus], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice metadata', + validationErrorMessage: "Failed to update invoice metadata", }); await context?.query({ text: queries.updateInvoicePaymentStatus, values: [invoicePaymentId, invoicePaymentStatus], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice payment metadata', + validationErrorMessage: "Failed to update invoice payment metadata", }); // set previous subscription to inactive await context?.query({ text: queries.updateSubscriptionStatus, values: [activeSubscriptionId, SubscriptionStatus.Inactive], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update previous subscription', + validationErrorMessage: "Failed to update previous subscription", }); // set current subscription as active await context?.query({ text: queries.updateSubscriptionStatus, values: [newSubscriptionId, SubscriptionStatus.Active], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update new subscription', + validationErrorMessage: "Failed to update new subscription", }); if (metadata) { await context?.query({ text: queries.updateSubscriptionMetadata, values: [newSubscriptionId, metadata], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update new subscription metadata', + validationErrorMessage: "Failed to update new subscription metadata", }); } context?.commitTransaction(); return true; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update', error); + this.logger.error("Failed to update", error); return false; } } @@ -475,18 +510,18 @@ export class BillingRepository { text: queries.updateInvoiceStatus, values: [invoiceId, invoiceStatus], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice metadata', + validationErrorMessage: "Failed to update invoice metadata", }); await context?.query({ text: queries.updateInvoicePaymentStatus, values: [invoicePaymentId, invoicePaymentStatus], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice payment metadata', + validationErrorMessage: "Failed to update invoice payment metadata", }); return true; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update', error); + this.logger.error("Failed to update", error); return false; } } @@ -503,13 +538,13 @@ export class BillingRepository { text: queries.updateInvoicePaymentMetadata, values: [invoicePaymentId, metadata], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update invoice payment metadata', + validationErrorMessage: "Failed to update invoice payment metadata", }); context?.commitTransaction(); return true; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update invoice payment metadata', error); + this.logger.error("Failed to update invoice payment metadata", error); return false; } } @@ -531,15 +566,25 @@ export class BillingRepository { const date = new Date(Date.now() + plan.duration * day); const createProjectSubscriptionResult = await context!.query({ text: queries.createProjectSubscription, - values: [projectId, plan.id, subscriptionMetaData, SubscriptionStatus.Inactive, date], + values: [ + projectId, + plan.id, + subscriptionMetaData, + SubscriptionStatus.Inactive, + date, + ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create subscription', + validationErrorMessage: "Failed to create subscription", }); const subscriptionId = createProjectSubscriptionResult.rows[0].id; // create subscription resources const resources = await this.getProjectPlanResources(plan.id, context!); - await this.addPlanResourceToSubscription(subscriptionId, resources, context!); + await this.addPlanResourceToSubscription( + subscriptionId, + resources, + context!, + ); // create subscription invoice const invoice: Invoice = { @@ -563,7 +608,7 @@ export class BillingRepository { invoice.dueDate, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create invoice', + validationErrorMessage: "Failed to create invoice", }); const invoiceId = createInvoiceResult?.rows[0].id; const invoicePayment: InvoicePayment = { @@ -588,7 +633,7 @@ export class BillingRepository { invoicePayment.status, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create invoice payment', + validationErrorMessage: "Failed to create invoice payment", }); const createInvoicePaymentId = createInvoicePaymentResult?.rows[0].id; @@ -601,7 +646,7 @@ export class BillingRepository { }; } catch (error) { context!.rollbackTransaction(); - this.logger.error('Failed to subscribe project to plan', error); + this.logger.error("Failed to subscribe project to plan", error); return undefined; } } @@ -627,13 +672,13 @@ export class BillingRepository { resource.overagesBundleCount, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to insert subscription resource', + validationErrorMessage: "Failed to insert subscription resource", }); }), ); return true; } catch (error) { - this.logger.error('Transaction failed:', error); + this.logger.error("Transaction failed:", error); await context.rollbackTransaction(); throw error; } @@ -669,7 +714,7 @@ export class BillingRepository { text: queries.getProjectActiveSubscriptionId, values: [projectId, SubscriptionStatus.Active], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to get project active subscription', + validationErrorMessage: "Failed to get project active subscription", }); if (!getProjectActiveSubscriptionResult?.rowCount) { return null; @@ -764,7 +809,9 @@ export class BillingRepository { } @withDbContext - async getAlmostDueSubscriptions(context?: DbContext): Promise { + async getAlmostDueSubscriptions( + context?: DbContext, + ): Promise { const now = new Date(); const in7Days = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); const result = await context?.query({ diff --git a/src/modules/repositories/billing/index.ts b/src/modules/repositories/billing/index.ts index 93524f7..3f7fc66 100644 --- a/src/modules/repositories/billing/index.ts +++ b/src/modules/repositories/billing/index.ts @@ -1,2 +1,2 @@ -export * from './billing.repository'; -export * from './types'; +export * from "./billing.repository"; +export * from "./types"; diff --git a/src/modules/repositories/billing/queries.ts b/src/modules/repositories/billing/queries.ts index e421fd1..64fda89 100644 --- a/src/modules/repositories/billing/queries.ts +++ b/src/modules/repositories/billing/queries.ts @@ -5,12 +5,14 @@ export const queries = { 'INSERT INTO "bonadocs"."invoice_payments" (invoice_id, payment_channel, payment_reference, amount, currency, date_created, status) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id;', createInvoiceItem: 'INSERT INTO "bonadocs"."invoice_items" (invoice_id, resource_id, quantity, amount, currency) VALUES ($1, $2, $3, $4, $5) RETURNING id;', - updateInvoiceStatus: 'UPDATE "bonadocs"."invoices" SET status = $2 WHERE id = $1', + updateInvoiceStatus: + 'UPDATE "bonadocs"."invoices" SET status = $2 WHERE id = $1', getInvoiceById: 'SELECT i.id, i.subscription_id, i.reference, i.amount, i.currency, i.status, i.date_created, i.due_date FROM "bonadocs"."invoices" i WHERE id = $1 LIMIT 1', getInvoiceBySubscriptionId: 'SELECT i.id, i.subscription_id, i.reference, i.amount, i.currency, i.status, i.date_created, i.due_date FROM "bonadocs"."invoices" i WHERE subscription_id = $1 LIMIT 1', - updateInvoicePaymentStatus: 'UPDATE "bonadocs"."invoice_payments" SET status = $2 WHERE id = $1', + updateInvoicePaymentStatus: + 'UPDATE "bonadocs"."invoice_payments" SET status = $2 WHERE id = $1', getInvoicePaymentById: 'SELECT ip.id, ip.invoice_id, ip.payment_channel, ip.payment_reference, ip.amount, ip.currency, ip.date_created, ip.status FROM "bonadocs"."invoice_payments" ip WHERE id = $1 LIMIT 1', updateInvoicePayment: @@ -23,12 +25,14 @@ export const queries = { 'SELECT p.id, p.name, p.price, p.currency, p.duration, p.active, p.date_created FROM "bonadocs"."plans" AS p', getPlanById: 'SELECT id, name, price, currency, duration, active, date_created, metadata FROM "bonadocs"."plans" WHERE id = $1 LIMIT 1', - updateSubscriptionMetadata: 'UPDATE "bonadocs"."subscriptions" SET metadata = $2 WHERE id = $1', + updateSubscriptionMetadata: + 'UPDATE "bonadocs"."subscriptions" SET metadata = $2 WHERE id = $1', updateInvoicePaymentMetadata: 'UPDATE "bonadocs"."invoice_payments" SET metadata = $2 WHERE id = $1', createProjectSubscription: 'INSERT INTO "bonadocs"."subscriptions" (project_id, plan_id, metadata, status, date_expires) VALUES ($1, $2, $3, $4, $5) RETURNING id', - updateSubscriptionStatus: 'UPDATE "bonadocs"."subscriptions" SET status = $2 WHERE id = $1', + updateSubscriptionStatus: + 'UPDATE "bonadocs"."subscriptions" SET status = $2 WHERE id = $1', insertSubscriptionResources: 'INSERT INTO "bonadocs"."subscription_resources" (subscription_id, resource_id, quantity, usage, overages_supported, overages_price, overages_bundle_count) VALUES ($1, $2, $3, $4, $5, $6, $7)', getPlanResources: diff --git a/src/modules/repositories/billing/types.ts b/src/modules/repositories/billing/types.ts index b692303..d11555d 100644 --- a/src/modules/repositories/billing/types.ts +++ b/src/modules/repositories/billing/types.ts @@ -1,5 +1,8 @@ -import { InvoicePaymentStatus, InvoiceStatus } from '../../shared/types/invoice'; -import { SubscriptionStatus } from '../../shared/types/subscriptions'; +import { + InvoicePaymentStatus, + InvoiceStatus, +} from "../../shared/types/invoice"; +import { SubscriptionStatus } from "../../shared/types/subscriptions"; export interface Invoice { subscriptionId: number; diff --git a/src/modules/repositories/coupon/coupon.repository.ts b/src/modules/repositories/coupon/coupon.repository.ts index 239d849..039825c 100644 --- a/src/modules/repositories/coupon/coupon.repository.ts +++ b/src/modules/repositories/coupon/coupon.repository.ts @@ -1,14 +1,14 @@ -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { DbContext, withDbContext } from '../../connection/dbcontext'; -import { SubscriptionStatus } from '../../shared/types/subscriptions'; -import { ProjectRepository } from '../projects'; +import { DbContext, withDbContext } from "../../connection/dbcontext"; +import { SubscriptionStatus } from "../../shared/types/subscriptions"; +import { ProjectRepository } from "../projects"; -import { queries } from './queries'; -import { CouponDto, CreateCouponDto } from './types'; +import { queries } from "./queries"; +import { CouponDto, CreateCouponDto } from "./types"; @Service() export class CouponRepository { @@ -18,7 +18,10 @@ export class CouponRepository { ) {} @withDbContext - async createCoupon(couponDto: CreateCouponDto, context?: DbContext): Promise { + async createCoupon( + couponDto: CreateCouponDto, + context?: DbContext, + ): Promise { try { context?.beginTransaction(); const createCoupon = await context?.query({ @@ -27,24 +30,30 @@ export class CouponRepository { couponDto.code, couponDto.project_id, couponDto.plan_id, - couponDto.status === 2 ? SubscriptionStatus.Active : SubscriptionStatus.Inactive, + couponDto.status === 2 + ? SubscriptionStatus.Active + : SubscriptionStatus.Inactive, couponDto.date_expire, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create coupon', + validationErrorMessage: "Failed to create coupon", }); const code = createCoupon?.rows[0].code; const couponId = createCoupon?.rows[0].id; // subscribe to plan - const plan = await this.projectRepository.getPlanById(couponDto.plan_id, context!); - - // deactivate any existing active subscription for the project - const existingSubscription = await this.projectRepository.getActiveSubscriptionsForProject( - couponDto.project_id, + const plan = await this.projectRepository.getPlanById( + couponDto.plan_id, context!, ); + // deactivate any existing active subscription for the project + const existingSubscription = + await this.projectRepository.getActiveSubscriptionsForProject( + couponDto.project_id, + context!, + ); + for (const subscription of existingSubscription) { // eslint-disable-next-line no-await-in-loop await this.projectRepository.changeSubscriptionStatus( @@ -54,25 +63,26 @@ export class CouponRepository { ); } - const subscriptionId = await this.projectRepository.subscribeProjectToPlan( - plan!, - couponDto.project_id, - context!, - couponDto.date_expire, - ); + const subscriptionId = + await this.projectRepository.subscribeProjectToPlan( + plan!, + couponDto.project_id, + context!, + couponDto.date_expire, + ); // add subscription id to coupon await context?.query({ text: queries.updateCouponSubscriptionId, values: [subscriptionId, couponId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update coupon subscription id', + validationErrorMessage: "Failed to update coupon subscription id", }); await context?.commitTransaction(); return code; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to create project', error); + this.logger.error("Failed to create project", error); return undefined; } } @@ -92,32 +102,42 @@ export class CouponRepository { context?.beginTransaction(); const updateCoupon = await context?.query({ text: queries.updateCouponStatus, - values: [status === 2 ? SubscriptionStatus.Active : SubscriptionStatus.Inactive, coupon.id], + values: [ + status === 2 + ? SubscriptionStatus.Active + : SubscriptionStatus.Inactive, + coupon.id, + ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update coupon status', + validationErrorMessage: "Failed to update coupon status", }); // update subscription status await context?.query({ text: queries.updateSubscriptionStatus, values: [ - status === 2 ? SubscriptionStatus.Active : SubscriptionStatus.Inactive, + status === 2 + ? SubscriptionStatus.Active + : SubscriptionStatus.Inactive, coupon.subscription_id, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update subscription status', + validationErrorMessage: "Failed to update subscription status", }); const code = updateCoupon?.rows[0].code; await context?.commitTransaction(); return code; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to change coupon status', error); + this.logger.error("Failed to change coupon status", error); return undefined; } } @withDbContext - async getCouponById(couponId: number, context?: DbContext): Promise { + async getCouponById( + couponId: number, + context?: DbContext, + ): Promise { try { const coupon = await context?.query({ text: queries.getCouponById, @@ -132,7 +152,9 @@ export class CouponRepository { date_expire: coupon.rows[0].date_expire, plan_id: coupon.rows[0].plan_id, status: - coupon.rows[0].status === 2 ? SubscriptionStatus.Active : SubscriptionStatus.Inactive, + coupon.rows[0].status === 2 + ? SubscriptionStatus.Active + : SubscriptionStatus.Inactive, subscription_id: coupon.rows[0].subscription_id, }; } @@ -144,7 +166,10 @@ export class CouponRepository { } @withDbContext - async getCouponByCode(code: string, context?: DbContext): Promise { + async getCouponByCode( + code: string, + context?: DbContext, + ): Promise { try { const coupon = await context?.query({ text: queries.getCouponByCode, @@ -159,7 +184,9 @@ export class CouponRepository { date_expire: coupon.rows[0].date_expire, plan_id: coupon.rows[0].plan_id, status: - coupon.rows[0].status === 2 ? SubscriptionStatus.Active : SubscriptionStatus.Inactive, + coupon.rows[0].status === 2 + ? SubscriptionStatus.Active + : SubscriptionStatus.Inactive, subscription_id: coupon.rows[0].subscription_id, }; } @@ -171,7 +198,10 @@ export class CouponRepository { } @withDbContext - async listCouponsByProjectId(projectId: number, context?: DbContext): Promise { + async listCouponsByProjectId( + projectId: number, + context?: DbContext, + ): Promise { const coupons = await context?.query({ text: queries.listCouponsByProjectId, values: [projectId], @@ -185,7 +215,10 @@ export class CouponRepository { date_created: row.date_created, date_expire: row.date_expire, plan_id: row.plan_id, - status: row.status === 2 ? SubscriptionStatus.Active : SubscriptionStatus.Inactive, + status: + row.status === 2 + ? SubscriptionStatus.Active + : SubscriptionStatus.Inactive, subscription_id: row.subscription_id, })); } @@ -211,7 +244,9 @@ export class CouponRepository { date_expire: coupons.rows[0].date_expire, plan_id: coupons.rows[0].plan_id, status: - coupons.rows[0].status === 2 ? SubscriptionStatus.Active : SubscriptionStatus.Inactive, + coupons.rows[0].status === 2 + ? SubscriptionStatus.Active + : SubscriptionStatus.Inactive, subscription_id: coupons.rows[0].subscription_id, }; } diff --git a/src/modules/repositories/coupon/index.ts b/src/modules/repositories/coupon/index.ts index 76e9efc..0ec3c7b 100644 --- a/src/modules/repositories/coupon/index.ts +++ b/src/modules/repositories/coupon/index.ts @@ -1,2 +1,2 @@ -export { CouponRepository } from './coupon.repository'; -export * from './types'; +export { CouponRepository } from "./coupon.repository"; +export * from "./types"; diff --git a/src/modules/repositories/coupon/queries.ts b/src/modules/repositories/coupon/queries.ts index d946425..f3a2a0a 100644 --- a/src/modules/repositories/coupon/queries.ts +++ b/src/modules/repositories/coupon/queries.ts @@ -1,17 +1,19 @@ export const queries = { createCoupon: - 'INSERT INTO bonadocs.coupons (code, project_id, plan_id, status, date_expires) VALUES ($1, $2, $3, $4, $5) RETURNING id, code;', - updateCouponStatus: 'UPDATE bonadocs.coupons SET status = $1 WHERE id = $2 RETURNING code;', + "INSERT INTO bonadocs.coupons (code, project_id, plan_id, status, date_expires) VALUES ($1, $2, $3, $4, $5) RETURNING id, code;", + updateCouponStatus: + "UPDATE bonadocs.coupons SET status = $1 WHERE id = $2 RETURNING code;", updateCouponSubscriptionId: - 'UPDATE bonadocs.coupons SET subscription_id = $1 WHERE id = $2 RETURNING code;', + "UPDATE bonadocs.coupons SET subscription_id = $1 WHERE id = $2 RETURNING code;", getCouponWithJoins: - 'SELECT * FROM bonadocs.coupons as c INNER JOIN bonadocs.projects as p on c.project_id = p.id INNER JOIN bonadocs.plans pl on pl.id = c.plan_id where c.id = $1', - listCoupons: 'SELECT * FROM bonadocs.coupons', - getCouponById: 'SELECT * FROM bonadocs.coupons WHERE id = $1', - getCouponByCode: 'SELECT * FROM bonadocs.coupons WHERE code = $1', + "SELECT * FROM bonadocs.coupons as c INNER JOIN bonadocs.projects as p on c.project_id = p.id INNER JOIN bonadocs.plans pl on pl.id = c.plan_id where c.id = $1", + listCoupons: "SELECT * FROM bonadocs.coupons", + getCouponById: "SELECT * FROM bonadocs.coupons WHERE id = $1", + getCouponByCode: "SELECT * FROM bonadocs.coupons WHERE code = $1", updateSubscriptionStatus: - 'UPDATE bonadocs.subscriptions SET status = $1 WHERE id = $2 RETURNING id;', - listCouponsByProjectId: 'SELECT * FROM bonadocs.coupons WHERE project_id = $1', + "UPDATE bonadocs.subscriptions SET status = $1 WHERE id = $2 RETURNING id;", + listCouponsByProjectId: + "SELECT * FROM bonadocs.coupons WHERE project_id = $1", getCouponsByProjectIdAndPlanId: - 'SELECT * FROM bonadocs.coupons WHERE project_id = $1 AND plan_id = $2 AND status = $3', + "SELECT * FROM bonadocs.coupons WHERE project_id = $1 AND plan_id = $2 AND status = $3", }; diff --git a/src/modules/repositories/coupon/types.ts b/src/modules/repositories/coupon/types.ts index 71f9c2c..375810d 100644 --- a/src/modules/repositories/coupon/types.ts +++ b/src/modules/repositories/coupon/types.ts @@ -1,5 +1,5 @@ -import { SubscriptionStatus } from '../../shared/types/subscriptions'; -import { PlanDto, ProjectDTO, SubscriptionDTO } from '../projects'; +import { SubscriptionStatus } from "../../shared/types/subscriptions"; +import { PlanDto, ProjectDTO, SubscriptionDTO } from "../projects"; export interface CreateCouponDto { project_id: number; diff --git a/src/modules/repositories/evm-contracts/evm-contracts.repository.test.ts b/src/modules/repositories/evm-contracts/evm-contracts.repository.test.ts index fe4d39f..4ee308e 100644 --- a/src/modules/repositories/evm-contracts/evm-contracts.repository.test.ts +++ b/src/modules/repositories/evm-contracts/evm-contracts.repository.test.ts @@ -1,17 +1,17 @@ -import { beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { getMockDbContext, getMockLogger } from '../../../test/util'; -import { DbContext } from '../../connection/dbcontext'; +import { getMockDbContext, getMockLogger } from "../../../test/util"; +import { DbContext } from "../../connection/dbcontext"; -import { EvmContractRepository } from './evm-contracts.repository'; -import { queries } from './queries'; +import { EvmContractRepository } from "./evm-contracts.repository"; +import { queries } from "./queries"; -jest.mock('../../connection/dbcontext'); -jest.mock('@bonadocs/logger'); +jest.mock("../../connection/dbcontext"); +jest.mock("@bonadocs/logger"); -describe('EvmContractRepository', () => { +describe("EvmContractRepository", () => { let evmContractsRepository: EvmContractRepository; let context: jest.Mocked; let logger: BonadocsLogger; @@ -22,72 +22,80 @@ describe('EvmContractRepository', () => { context = getMockDbContext(); }); - describe('getContractAbiHash', () => { - it('should return null if no contract is found', async () => { + describe("getContractAbiHash", () => { + it("should return null if no contract is found", async () => { context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, fields: [], - command: '', + command: "", }); - const result = await evmContractsRepository.getContractAbiHash(1, '0x123', context); + const result = await evmContractsRepository.getContractAbiHash( + 1, + "0x123", + context, + ); expect(result).toBeNull(); expect(context.query).toHaveBeenCalledWith({ text: queries.getContractAbiHash, - values: [1, '0x123'], + values: [1, "0x123"], }); }); - it('should return abi_hash if contract is found', async () => { - const mockAbiHash = 'mockAbiHash'; + it("should return abi_hash if contract is found", async () => { + const mockAbiHash = "mockAbiHash"; context.query.mockResolvedValue({ rowCount: 1, rows: [{ abi_hash: mockAbiHash }], oid: 0, fields: [], - command: '', + command: "", }); - const result = await evmContractsRepository.getContractAbiHash(1, '0x123', context); + const result = await evmContractsRepository.getContractAbiHash( + 1, + "0x123", + context, + ); expect(result).toBe(mockAbiHash); expect(context.query).toHaveBeenCalledWith({ text: queries.getContractAbiHash, - values: [1, '0x123'], + values: [1, "0x123"], }); }); }); - describe('createContract', () => { - it('should return true if contract is created successfully', async () => { + describe("createContract", () => { + it("should return true if contract is created successfully", async () => { context.query.mockResolvedValue({ rowCount: 1, rows: [{ id: 1 }], oid: 0, fields: [], - command: '', + command: "", }); const result = await evmContractsRepository.createContract( - { address: '0x123', chainId: 1, abiHash: 'mockAbiHash' }, + { address: "0x123", chainId: 1, abiHash: "mockAbiHash" }, context, ); expect(result).toBe(true); }); - it('should return false if contract creation fails', async () => { + it("should return false if contract creation fails", async () => { context.query.mockResolvedValue({ rowCount: 0, rows: [{ id: 1 }], oid: 0, fields: [], - command: '', + command: "", }); const result = await evmContractsRepository.createContract( - { address: '0x123', chainId: 1, abiHash: 'mockAbiHash' }, + { address: "0x123", chainId: 1, abiHash: "mockAbiHash" }, context, ); diff --git a/src/modules/repositories/evm-contracts/evm-contracts.repository.ts b/src/modules/repositories/evm-contracts/evm-contracts.repository.ts index da62deb..f8b67fb 100644 --- a/src/modules/repositories/evm-contracts/evm-contracts.repository.ts +++ b/src/modules/repositories/evm-contracts/evm-contracts.repository.ts @@ -1,16 +1,18 @@ -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { DbContext, withDbContext } from '../../connection/dbcontext'; +import { DbContext, withDbContext } from "../../connection/dbcontext"; -import { queries } from './queries'; -import { CreateContractDto } from './types'; +import { queries } from "./queries"; +import { CreateContractDto } from "./types"; @Service() export class EvmContractRepository { - constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} + constructor( + @Inject(diConstants.logger) private readonly logger: BonadocsLogger, + ) {} @withDbContext async getContractAbiHash( @@ -31,14 +33,17 @@ export class EvmContractRepository { } @withDbContext - async createContract(data: CreateContractDto, context?: DbContext): Promise { + async createContract( + data: CreateContractDto, + context?: DbContext, + ): Promise { try { context?.beginTransaction(); const result = await context!.query({ text: queries.insertContract, values: [data.address, data.chainId, data.abiHash], validateResult: (res) => !!res.rowCount, - validationErrorMessage: 'Failed to create contract', + validationErrorMessage: "Failed to create contract", }); context?.commitTransaction(); return !!result.rowCount; diff --git a/src/modules/repositories/evm-contracts/index.ts b/src/modules/repositories/evm-contracts/index.ts index 5c72842..48e4a41 100644 --- a/src/modules/repositories/evm-contracts/index.ts +++ b/src/modules/repositories/evm-contracts/index.ts @@ -1,2 +1,2 @@ -export { EvmContractRepository } from './evm-contracts.repository'; -export * from './types'; +export { EvmContractRepository } from "./evm-contracts.repository"; +export * from "./types"; diff --git a/src/modules/repositories/index.ts b/src/modules/repositories/index.ts index f72b083..30860e9 100644 --- a/src/modules/repositories/index.ts +++ b/src/modules/repositories/index.ts @@ -1,2 +1,2 @@ -export * from './sample'; -export * from './users'; +export * from "./sample"; +export * from "./users"; diff --git a/src/modules/repositories/projects/index.ts b/src/modules/repositories/projects/index.ts index 59922cc..dc3a040 100644 --- a/src/modules/repositories/projects/index.ts +++ b/src/modules/repositories/projects/index.ts @@ -1,2 +1,2 @@ -export { ProjectRepository } from './project.repository'; -export * from './types'; +export { ProjectRepository } from "./project.repository"; +export * from "./types"; diff --git a/src/modules/repositories/projects/project.repository.test.ts b/src/modules/repositories/projects/project.repository.test.ts index 8735b45..a3de2cf 100644 --- a/src/modules/repositories/projects/project.repository.test.ts +++ b/src/modules/repositories/projects/project.repository.test.ts @@ -1,20 +1,25 @@ -import 'reflect-metadata'; -import { beforeEach, describe, expect, it, jest } from '@jest/globals'; -import { QueryResult } from 'pg'; - -import { BonadocsLogger } from '@bonadocs/logger'; - -import { getMockDbContext, getMockLogger } from '../../../test/util'; -import { DbContext } from '../../connection/dbcontext'; - -import { ProjectRepository } from './project.repository'; -import { queries } from './queries'; -import { CreateProjectDto, GenerateApiKeyDto, InviteUserToProjectDto, PlanDto } from './types'; -import { flagsToPermissions, PermissionNames } from './util'; - -jest.mock('../../connection/dbcontext'); -jest.mock('@bonadocs/logger'); -describe('ProjectRepository', () => { +import "reflect-metadata"; +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; +import { QueryResult } from "pg"; + +import { BonadocsLogger } from "@bonadocs/logger"; + +import { getMockDbContext, getMockLogger } from "../../../test/util"; +import { DbContext } from "../../connection/dbcontext"; + +import { ProjectRepository } from "./project.repository"; +import { queries } from "./queries"; +import { + CreateProjectDto, + GenerateApiKeyDto, + InviteUserToProjectDto, + PlanDto, +} from "./types"; +import { flagsToPermissions, PermissionNames } from "./util"; + +jest.mock("../../connection/dbcontext"); +jest.mock("@bonadocs/logger"); +describe("ProjectRepository", () => { let projectRepository: ProjectRepository; let context: jest.Mocked; let logger: jest.Mocked; @@ -25,15 +30,19 @@ describe('ProjectRepository', () => { projectRepository = new ProjectRepository(logger); }); - describe('createProject', () => { - it('should create a project and return its ID', async () => { - const data: CreateProjectDto = { name: 'Test Project', creatorId: 1, slug: 'test' }; + describe("createProject", () => { + it("should create a project and return its ID", async () => { + const data: CreateProjectDto = { + name: "Test Project", + creatorId: 1, + slug: "test", + }; context.query.mockResolvedValue({ rowCount: 1, rows: [{ id: 1 }], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); const result = await projectRepository.createProject(data, context); @@ -47,26 +56,33 @@ describe('ProjectRepository', () => { ); }); - it('should return undefined if project creation fails', async () => { - const data: CreateProjectDto = { name: 'Test Project', creatorId: 1, slug: 'test' }; - context.query.mockRejectedValue(new Error('Failed to create project')); + it("should return undefined if project creation fails", async () => { + const data: CreateProjectDto = { + name: "Test Project", + creatorId: 1, + slug: "test", + }; + context.query.mockRejectedValue(new Error("Failed to create project")); const result = await projectRepository.createProject(data, context); expect(result).toBeUndefined(); - expect(logger.error).toHaveBeenCalledWith('Failed to create project', expect.any(Error)); + expect(logger.error).toHaveBeenCalledWith( + "Failed to create project", + expect.any(Error), + ); }); }); - describe('subscribeProjectToPlan', () => { - it('should subscribe a project to a plan and return the subscription ID', async () => { + describe("subscribeProjectToPlan", () => { + it("should subscribe a project to a plan and return the subscription ID", async () => { const plan: PlanDto = { id: 1, duration: 30, price: 100, active: false, - currency: 'NGN', - name: 'TestProject', + currency: "NGN", + name: "TestProject", resources: [], }; const projectId = 1; @@ -75,10 +91,14 @@ describe('ProjectRepository', () => { rows: [{ id: 1 }], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); - const result = await projectRepository.subscribeProjectToPlan(plan, plan.id, context); + const result = await projectRepository.subscribeProjectToPlan( + plan, + plan.id, + context, + ); expect(result).toBe(1); expect(context.query).toHaveBeenCalledWith( @@ -89,14 +109,14 @@ describe('ProjectRepository', () => { ); }); - it('should return undefined if subscription fails', async () => { + it("should return undefined if subscription fails", async () => { const plan: PlanDto = { id: 1, duration: 30, price: 100, active: false, - currency: 'NGN', - name: 'TestProject', + currency: "NGN", + name: "TestProject", resources: [], }; const projectId = 1; @@ -105,25 +125,29 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); - const result = await projectRepository.subscribeProjectToPlan(plan, projectId, context); + const result = await projectRepository.subscribeProjectToPlan( + plan, + projectId, + context, + ); expect(result).toBeUndefined(); expect(logger.error).toHaveBeenCalledWith( - 'Failed to subscribe project to plan', + "Failed to subscribe project to plan", expect.any(Error), ); }); }); - describe('generateApiKey', () => { - it('should add a new API key if force is false', async () => { + describe("generateApiKey", () => { + it("should add a new API key if force is false", async () => { const data: GenerateApiKeyDto = { projectId: 1, - name: 'Test Key', - apiKeyHash: 'hash', + name: "Test Key", + apiKeyHash: "hash", expiryDate: new Date(), }; context.query.mockResolvedValue({ @@ -131,7 +155,7 @@ describe('ProjectRepository', () => { rows: [{ projectId: 1 }], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); await projectRepository.generateApiKey(data, false, context); @@ -144,11 +168,11 @@ describe('ProjectRepository', () => { ); }); - it('should update an existing API key if force is true', async () => { + it("should update an existing API key if force is true", async () => { const data: GenerateApiKeyDto = { projectId: 1, - name: 'Test Key', - apiKeyHash: 'hash', + name: "Test Key", + apiKeyHash: "hash", expiryDate: new Date(), }; context.query.mockResolvedValue({ @@ -156,7 +180,7 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); await projectRepository.generateApiKey(data, true, context); @@ -170,11 +194,11 @@ describe('ProjectRepository', () => { }); }); - describe('inviteUserToProject', () => { - it('should invite a user to a project', async () => { + describe("inviteUserToProject", () => { + it("should invite a user to a project", async () => { const data: InviteUserToProjectDto = { projectId: 1, - emailAddress: 'test@example.com', + emailAddress: "test@example.com", permissions: [], }; context.query.mockResolvedValue({ @@ -182,7 +206,7 @@ describe('ProjectRepository', () => { rows: [{ projectId: 1 }], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); await projectRepository.inviteUserToProject(data, context); @@ -196,28 +220,30 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if invitation fails', async () => { + it("should throw an error if invitation fails", async () => { const data: InviteUserToProjectDto = { projectId: 1, - emailAddress: 'test@example.com', + emailAddress: "test@example.com", permissions: [], }; - context.query.mockRejectedValue(new Error('Failed to invite user to project')); - - await expect(projectRepository.inviteUserToProject(data, context)).rejects.toThrow( - 'Failed to invite user to project', + context.query.mockRejectedValue( + new Error("Failed to invite user to project"), ); + + await expect( + projectRepository.inviteUserToProject(data, context), + ).rejects.toThrow("Failed to invite user to project"); }); }); - describe('deleteProject', () => { - it('should delete a project successfully', async () => { + describe("deleteProject", () => { + it("should delete a project successfully", async () => { const projectId = 1; context.query.mockResolvedValue({ rowCount: 1, rows: [], oid: 0, fields: [], - command: 'DELETE', + command: "DELETE", }); await projectRepository.deleteProject(projectId, context); @@ -231,24 +257,24 @@ describe('ProjectRepository', () => { expect(context.commitTransaction).toHaveBeenCalled(); }); - it('should throw an error if project deletion fails', async () => { + it("should throw an error if project deletion fails", async () => { const projectId = 1; - context.query.mockRejectedValue(new Error('Failed to delete project')); + context.query.mockRejectedValue(new Error("Failed to delete project")); - await expect(projectRepository.deleteProject(projectId, context)).rejects.toThrow( - 'Failed to delete project', - ); + await expect( + projectRepository.deleteProject(projectId, context), + ).rejects.toThrow("Failed to delete project"); expect(context.rollbackTransaction).toHaveBeenCalled(); }); }); - describe('getProjectById', () => { - it('should return project details if project exists', async () => { + describe("getProjectById", () => { + it("should return project details if project exists", async () => { const projectId = 1; const projectData = { id: projectId, - slug: 'test-slug', - name: 'Test Project', + slug: "test-slug", + name: "Test Project", date_created: new Date(), }; @@ -257,7 +283,7 @@ describe('ProjectRepository', () => { rows: [projectData], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await projectRepository.getProjectById(projectId, context); @@ -276,14 +302,14 @@ describe('ProjectRepository', () => { ); }); - it('should return null if project does not exist', async () => { + it("should return null if project does not exist", async () => { const projectId = 1; context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await projectRepository.getProjectById(projectId, context); @@ -297,23 +323,23 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if query fails', async () => { + it("should throw an error if query fails", async () => { const projectId = 1; - context.query.mockRejectedValue(new Error('Query failed')); + context.query.mockRejectedValue(new Error("Query failed")); - await expect(projectRepository.getProjectById(projectId, context)).rejects.toThrow( - 'Query failed', - ); + await expect( + projectRepository.getProjectById(projectId, context), + ).rejects.toThrow("Query failed"); }); }); - describe('getProjectBySlug', () => { - it('should return project details if project exists', async () => { - const slug = 'test-slug'; + describe("getProjectBySlug", () => { + it("should return project details if project exists", async () => { + const slug = "test-slug"; const projectData = { id: 1, slug, - name: 'Test Project', + name: "Test Project", date_created: new Date(), }; context.query.mockResolvedValue({ @@ -321,7 +347,7 @@ describe('ProjectRepository', () => { rows: [projectData], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await projectRepository.getProjectBySlug(slug, context); @@ -340,15 +366,15 @@ describe('ProjectRepository', () => { ); }); - it('should return null if project does not exist', async () => { - const slug = 'test-slug'; + it("should return null if project does not exist", async () => { + const slug = "test-slug"; context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await projectRepository.getProjectBySlug(slug, context); @@ -362,28 +388,28 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if query fails', async () => { - const slug = 'test-slug'; + it("should throw an error if query fails", async () => { + const slug = "test-slug"; (context.query as jest.Mock).mockImplementation(() => - Promise.reject(new Error('Query failed')), + Promise.reject(new Error("Query failed")), ); - context.query.mockRejectedValue(new Error('Query failed')); + context.query.mockRejectedValue(new Error("Query failed")); - await expect(projectRepository.getProjectBySlug(slug, context)).rejects.toThrow( - 'Query failed', - ); + await expect( + projectRepository.getProjectBySlug(slug, context), + ).rejects.toThrow("Query failed"); }); }); - describe('getProjectsByUserId', () => { - it('should return a list of projects for a given user ID', async () => { + describe("getProjectsByUserId", () => { + it("should return a list of projects for a given user ID", async () => { const userId = 1; const projects = [ { id: 1, - slug: 'test-slug', - name: 'Test Project', + slug: "test-slug", + name: "Test Project", date_created: new Date(), permission_flags: 1, }, @@ -394,10 +420,13 @@ describe('ProjectRepository', () => { rows: projects, oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); - const result = await projectRepository.getProjectsByUserId(userId, context); + const result = await projectRepository.getProjectsByUserId( + userId, + context, + ); expect(result).toEqual( projects.map((project) => ({ @@ -416,17 +445,20 @@ describe('ProjectRepository', () => { ); }); - it('should return an empty array if no projects are found', async () => { + it("should return an empty array if no projects are found", async () => { const userId = 1; context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); - const result = await projectRepository.getProjectsByUserId(userId, context); + const result = await projectRepository.getProjectsByUserId( + userId, + context, + ); expect(result).toEqual([]); expect(context.query).toHaveBeenCalledWith( @@ -437,24 +469,24 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if query fails', async () => { + it("should throw an error if query fails", async () => { const userId = 1; - context.query.mockRejectedValue(new Error('Query failed')); + context.query.mockRejectedValue(new Error("Query failed")); - await expect(projectRepository.getProjectsByUserId(userId, context)).rejects.toThrow( - 'Query failed', - ); + await expect( + projectRepository.getProjectsByUserId(userId, context), + ).rejects.toThrow("Query failed"); }); }); - describe('getProjectByAPIKeyHash', () => { - it('should return project details if project exists', async () => { - const apiKeyHash = 'test-hash'; + describe("getProjectByAPIKeyHash", () => { + it("should return project details if project exists", async () => { + const apiKeyHash = "test-hash"; const projectData = { id: 1, - slug: 'test-slug', - name: 'Test Project', + slug: "test-slug", + name: "Test Project", date_created: new Date(), }; @@ -463,10 +495,13 @@ describe('ProjectRepository', () => { rows: [projectData], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); - const result = await projectRepository.getProjectByAPIKeyHash(apiKeyHash, context); + const result = await projectRepository.getProjectByAPIKeyHash( + apiKeyHash, + context, + ); expect(result).toEqual({ id: projectData.id, @@ -482,18 +517,21 @@ describe('ProjectRepository', () => { ); }); - it('should return null if project does not exist', async () => { - const apiKeyHash = 'test-hash'; + it("should return null if project does not exist", async () => { + const apiKeyHash = "test-hash"; context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); - const result = await projectRepository.getProjectByAPIKeyHash(apiKeyHash, context); + const result = await projectRepository.getProjectByAPIKeyHash( + apiKeyHash, + context, + ); expect(result).toBeNull(); expect(context.query).toHaveBeenCalledWith( @@ -504,22 +542,22 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if query fails', async () => { - const apiKeyHash = 'test-hash'; + it("should throw an error if query fails", async () => { + const apiKeyHash = "test-hash"; - context.query.mockRejectedValue(new Error('Query failed')); + context.query.mockRejectedValue(new Error("Query failed")); - await expect(projectRepository.getProjectByAPIKeyHash(apiKeyHash, context)).rejects.toThrow( - 'Query failed', - ); + await expect( + projectRepository.getProjectByAPIKeyHash(apiKeyHash, context), + ).rejects.toThrow("Query failed"); }); }); - describe('generateApiKey', () => { - it('should add a new API key if force is false', async () => { + describe("generateApiKey", () => { + it("should add a new API key if force is false", async () => { const data: GenerateApiKeyDto = { projectId: 1, - name: 'Test Key', - apiKeyHash: 'hash', + name: "Test Key", + apiKeyHash: "hash", expiryDate: new Date(), }; @@ -528,7 +566,7 @@ describe('ProjectRepository', () => { rows: [{ projectId: 1 }], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); await projectRepository.generateApiKey(data, false, context); @@ -541,11 +579,11 @@ describe('ProjectRepository', () => { ); }); - it('should update an existing API key if force is true', async () => { + it("should update an existing API key if force is true", async () => { const data: GenerateApiKeyDto = { projectId: 1, - name: 'Test Key', - apiKeyHash: 'hash', + name: "Test Key", + apiKeyHash: "hash", expiryDate: new Date(), }; @@ -554,7 +592,7 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); await projectRepository.generateApiKey(data, true, context); @@ -567,16 +605,16 @@ describe('ProjectRepository', () => { ); }); }); - describe('getProjectUsers', () => { - it('should return project users if users exist', async () => { + describe("getProjectUsers", () => { + it("should return project users if users exist", async () => { const projectId = 1; const users = [ { user_id: 1, - username: 'testuser', - email_address: 'test@example.com', - first_name: 'Test', - last_name: 'User', + username: "testuser", + email_address: "test@example.com", + first_name: "Test", + last_name: "User", permission_flags: 1, }, ]; @@ -586,10 +624,13 @@ describe('ProjectRepository', () => { rows: users, oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); - const result = await projectRepository.getProjectUsers(projectId, context); + const result = await projectRepository.getProjectUsers( + projectId, + context, + ); expect(result).toEqual({ id: projectId, @@ -610,7 +651,7 @@ describe('ProjectRepository', () => { ); }); - it('should return undefined if no users are found', async () => { + it("should return undefined if no users are found", async () => { const projectId = 1; context.query.mockResolvedValue({ @@ -618,10 +659,13 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); - const result = await projectRepository.getProjectUsers(projectId, context); + const result = await projectRepository.getProjectUsers( + projectId, + context, + ); expect(result).toBeUndefined(); expect(context.query).toHaveBeenCalledWith( @@ -632,18 +676,18 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if query fails', async () => { + it("should throw an error if query fails", async () => { const projectId = 1; - context.query.mockRejectedValue(new Error('Query failed')); + context.query.mockRejectedValue(new Error("Query failed")); - await expect(projectRepository.getProjectUsers(projectId, context)).rejects.toThrow( - 'Query failed', - ); + await expect( + projectRepository.getProjectUsers(projectId, context), + ).rejects.toThrow("Query failed"); }); }); - describe('updateProjectUserPermissions', () => { - it('should update user permissions for a project', async () => { + describe("updateProjectUserPermissions", () => { + it("should update user permissions for a project", async () => { const projectId = 1; const userId = 1; const permissionsToFlag = PermissionNames; @@ -653,7 +697,7 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'UPDATE', + command: "UPDATE", }); await projectRepository.updateProjectUserPermissions( @@ -672,31 +716,42 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if updating permissions fails', async () => { + it("should throw an error if updating permissions fails", async () => { const projectId = 1; const userId = 1; - context.query.mockRejectedValue(new Error('Failed to update project user permissions')); + context.query.mockRejectedValue( + new Error("Failed to update project user permissions"), + ); await expect( - projectRepository.updateProjectUserPermissions(projectId, userId, PermissionNames, context), - ).rejects.toThrow('Failed to update project user permissions'); + projectRepository.updateProjectUserPermissions( + projectId, + userId, + PermissionNames, + context, + ), + ).rejects.toThrow("Failed to update project user permissions"); }); }); - describe('cancelProjectInvitation', () => { - it('should cancel a project invitation successfully', async () => { + describe("cancelProjectInvitation", () => { + it("should cancel a project invitation successfully", async () => { const projectId = 1; - const emailAddress = 'test@example.com'; + const emailAddress = "test@example.com"; context.query.mockResolvedValue({ rowCount: 1, rows: [], oid: 0, fields: [], - command: 'DELETE', + command: "DELETE", }); - await projectRepository.cancelProjectInvitation(projectId, emailAddress, context); + await projectRepository.cancelProjectInvitation( + projectId, + emailAddress, + context, + ); expect(context.query).toHaveBeenCalledWith( expect.objectContaining({ @@ -706,20 +761,26 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if invitation cancellation fails', async () => { + it("should throw an error if invitation cancellation fails", async () => { const projectId = 1; - const emailAddress = 'test@example.com'; + const emailAddress = "test@example.com"; - context.query.mockRejectedValue(new Error('Failed to cancel project invitation')); + context.query.mockRejectedValue( + new Error("Failed to cancel project invitation"), + ); await expect( - projectRepository.cancelProjectInvitation(projectId, emailAddress, context), - ).rejects.toThrow('Failed to cancel project invitation'); + projectRepository.cancelProjectInvitation( + projectId, + emailAddress, + context, + ), + ).rejects.toThrow("Failed to cancel project invitation"); }); }); - describe('revokeAPIKey', () => { - it('should revoke an API key successfully', async () => { + describe("revokeAPIKey", () => { + it("should revoke an API key successfully", async () => { const projectId = 1; const apiKeyId = 1; @@ -729,7 +790,7 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'DELETE', + command: "DELETE", } as QueryResult), ); @@ -738,7 +799,7 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'DELETE', + command: "DELETE", }); await projectRepository.revokeAPIKey(projectId, apiKeyId, context); @@ -751,29 +812,29 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if revoking API key fails', async () => { + it("should throw an error if revoking API key fails", async () => { const projectId = 1; const apiKeyId = 1; (context.query as jest.Mock).mockImplementation(() => - Promise.reject(new Error('Failed to revoke API key')), + Promise.reject(new Error("Failed to revoke API key")), ); - context.query.mockRejectedValue(new Error('Failed to revoke API key')); + context.query.mockRejectedValue(new Error("Failed to revoke API key")); - await expect(projectRepository.revokeAPIKey(projectId, apiKeyId, context)).rejects.toThrow( - 'Failed to revoke API key', - ); + await expect( + projectRepository.revokeAPIKey(projectId, apiKeyId, context), + ).rejects.toThrow("Failed to revoke API key"); }); }); - describe('getProjectApiKeys', () => { - it('should return a list of API keys for a given project ID', async () => { + describe("getProjectApiKeys", () => { + it("should return a list of API keys for a given project ID", async () => { const projectId = 1; const apiKeys = [ { id: 1, - name: 'Test Key', + name: "Test Key", date_created: new Date(), date_expires: new Date(), }, @@ -784,10 +845,13 @@ describe('ProjectRepository', () => { rows: apiKeys, oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); - const result = await projectRepository.getProjectApiKeys(projectId, context); + const result = await projectRepository.getProjectApiKeys( + projectId, + context, + ); expect(result).toEqual( apiKeys.map((key) => ({ @@ -805,7 +869,7 @@ describe('ProjectRepository', () => { ); }); - it('should return an empty array if no API keys are found', async () => { + it("should return an empty array if no API keys are found", async () => { const projectId = 1; context.query.mockResolvedValue({ @@ -813,10 +877,13 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); - const result = await projectRepository.getProjectApiKeys(projectId, context); + const result = await projectRepository.getProjectApiKeys( + projectId, + context, + ); expect(result).toEqual([]); expect(context.query).toHaveBeenCalledWith( @@ -827,19 +894,19 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if query fails', async () => { + it("should throw an error if query fails", async () => { const projectId = 1; - context.query.mockRejectedValue(new Error('Query failed')); + context.query.mockRejectedValue(new Error("Query failed")); - await expect(projectRepository.getProjectApiKeys(projectId, context)).rejects.toThrow( - 'Query failed', - ); + await expect( + projectRepository.getProjectApiKeys(projectId, context), + ).rejects.toThrow("Query failed"); }); }); - describe('removeProjectUser', () => { - it('should remove a user from a project successfully', async () => { + describe("removeProjectUser", () => { + it("should remove a user from a project successfully", async () => { const projectId = 1; const userId = 1; @@ -848,7 +915,7 @@ describe('ProjectRepository', () => { rows: [], oid: 0, fields: [], - command: 'DELETE', + command: "DELETE", }); await projectRepository.removeProjectUser(projectId, userId, context); @@ -861,15 +928,17 @@ describe('ProjectRepository', () => { ); }); - it('should throw an error if removing user from project fails', async () => { + it("should throw an error if removing user from project fails", async () => { const projectId = 1; const userId = 1; - context.query.mockRejectedValue(new Error('Failed to remove user from project')); - - await expect(projectRepository.removeProjectUser(projectId, userId, context)).rejects.toThrow( - 'Failed to remove user from project', + context.query.mockRejectedValue( + new Error("Failed to remove user from project"), ); + + await expect( + projectRepository.removeProjectUser(projectId, userId, context), + ).rejects.toThrow("Failed to remove user from project"); }); }); }); diff --git a/src/modules/repositories/projects/project.repository.ts b/src/modules/repositories/projects/project.repository.ts index 988da50..85ad32e 100644 --- a/src/modules/repositories/projects/project.repository.ts +++ b/src/modules/repositories/projects/project.repository.ts @@ -1,16 +1,16 @@ -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { DbContext, withDbContext } from '../../connection/dbcontext'; +import { DbContext, withDbContext } from "../../connection/dbcontext"; import { SubscriptionPlan, SubscriptionResource, SubscriptionStatus, -} from '../../shared/types/subscriptions'; +} from "../../shared/types/subscriptions"; -import { queries } from './queries'; +import { queries } from "./queries"; import { ApiKeyDto, CreateProjectDto, @@ -27,18 +27,20 @@ import { ProjectUsersDto, SubscriptionDTO, updateProjectMetadataDTO, -} from './types'; +} from "./types"; import { convertSubscriptionResultSet, flagsToPermissions, hasPermission, PermissionName, ProjectPermissions, -} from './util'; +} from "./util"; @Service() export class ProjectRepository { - constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} + constructor( + @Inject(diConstants.logger) private readonly logger: BonadocsLogger, + ) {} @withDbContext async changeSubscriptionStatus( @@ -53,25 +55,28 @@ export class ProjectRepository { text: queries.changeSubscriptionStatus, values: [subscriptionId, projectId, status], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to change subscription status', + validationErrorMessage: "Failed to change subscription status", }); context?.commitTransaction(); } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to change subscription status', error); + this.logger.error("Failed to change subscription status", error); throw error; } } @withDbContext - async createProject(data: CreateProjectDto, context?: DbContext): Promise { + async createProject( + data: CreateProjectDto, + context?: DbContext, + ): Promise { try { context?.beginTransaction(); const createProjectResult = await context?.query({ text: queries.createProject, values: [data.slug, data.name], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create project', + validationErrorMessage: "Failed to create project", }); const projectId = createProjectResult?.rows[0].id; @@ -85,7 +90,7 @@ export class ProjectRepository { text: queries.addUserToProject, values: [projectId, data.creatorId, ProjectPermissions.admin], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to add user to project', + validationErrorMessage: "Failed to add user to project", }); await context?.commitTransaction(); @@ -93,7 +98,7 @@ export class ProjectRepository { return projectId; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to create project', error); + this.logger.error("Failed to create project", error); return undefined; } } @@ -108,7 +113,7 @@ export class ProjectRepository { text: queries.updateProjectMetadata, values: [projectId, metaData], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update project metadata', + validationErrorMessage: "Failed to update project metadata", }); } @@ -131,15 +136,19 @@ export class ProjectRepository { dateExpire ?? new Date(Date.now() + plan.duration * day), ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to create subscription', + validationErrorMessage: "Failed to create subscription", }); const subscriptionId = createProjectSubscriptionResult.rows[0].id; // create subscription resources - await this.addPlanResourceToSubscription(subscriptionId, plan.resources, context!); + await this.addPlanResourceToSubscription( + subscriptionId, + plan.resources, + context!, + ); return subscriptionId; } catch (error) { context!.rollbackTransaction(); - this.logger.error('Failed to subscribe project to plan', error); + this.logger.error("Failed to subscribe project to plan", error); return undefined; } } @@ -165,13 +174,13 @@ export class ProjectRepository { resource.overagesBundleCount, ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to insert subscription resource', + validationErrorMessage: "Failed to insert subscription resource", }); }), ); return true; } catch (error) { - this.logger.error('Transaction failed:', error); + this.logger.error("Transaction failed:", error); await context.rollbackTransaction(); throw error; } @@ -185,18 +194,21 @@ export class ProjectRepository { text: queries.deleteProject, values: [projectId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to delete project', + validationErrorMessage: "Failed to delete project", }); await context!.commitTransaction(); } catch (error) { await context!.rollbackTransaction(); - this.logger.error('Transaction failed:', error); + this.logger.error("Transaction failed:", error); throw error; } } @withDbContext - async getProjectById(id: number, context?: DbContext): Promise { + async getProjectById( + id: number, + context?: DbContext, + ): Promise { const result = await context!.query({ text: queries.getProjectById, values: [id], @@ -226,11 +238,12 @@ export class ProjectRepository { } const users = await this.getProjectUsers(projectId, context!); - const activeSubscription = await this.getActiveSubscriptionForProjectResource( - projectId, - SubscriptionResource.users, - context!, - ); + const activeSubscription = + await this.getActiveSubscriptionForProjectResource( + projectId, + SubscriptionResource.users, + context!, + ); return { ...project, @@ -245,7 +258,10 @@ export class ProjectRepository { resource: SubscriptionResource, dbContext: DbContext, ): Promise { - const subscriptions = await this.getActiveSubscriptionsForProject(projectId, dbContext); + const subscriptions = await this.getActiveSubscriptionsForProject( + projectId, + dbContext, + ); const subscriptionWithResource = subscriptions.find((s) => s.resources.some((r) => resource === r.resource && r.usage < r.limit), ); @@ -254,12 +270,18 @@ export class ProjectRepository { } return subscriptions.find((s) => - s.resources.some((r) => resource === r.resource && r.usage >= r.limit && r.overagesSupported), + s.resources.some( + (r) => + resource === r.resource && r.usage >= r.limit && r.overagesSupported, + ), ); } @withDbContext - async getProjectBySlug(slug: string, context?: DbContext): Promise { + async getProjectBySlug( + slug: string, + context?: DbContext, + ): Promise { const result = await context!.query({ text: queries.getProjectBySlug, values: [slug], @@ -279,7 +301,10 @@ export class ProjectRepository { } @withDbContext - async getProjectsByUserId(userId: number, context?: DbContext): Promise { + async getProjectsByUserId( + userId: number, + context?: DbContext, + ): Promise { const result = await context!.query({ text: queries.getProjectsByUserId, values: [userId], @@ -317,12 +342,16 @@ export class ProjectRepository { } @withDbContext - async revokeAPIKey(projectId: number, id: number, context?: DbContext): Promise { + async revokeAPIKey( + projectId: number, + id: number, + context?: DbContext, + ): Promise { await context!.query({ text: queries.revokeApiKey, values: [id, projectId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to revoke API key', + validationErrorMessage: "Failed to revoke API key", }); } @@ -339,26 +368,29 @@ export class ProjectRepository { text: queries.addProjectApiKey, values: [data.projectId, data.name, data.apiKeyHash, data.expiryDate], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to add API key', + validationErrorMessage: "Failed to add API key", }); } else { await context!.query({ text: queries.updateProjectApiKey, values: [data.projectId, data.name, data.apiKeyHash, data.expiryDate], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update API key', + validationErrorMessage: "Failed to update API key", }); } context?.commitTransaction(); } catch (error) { - this.logger.error('Transaction failed:', error); + this.logger.error("Transaction failed:", error); await context?.rollbackTransaction(); throw error; } } @withDbContext - async getProjectApiKeys(projectId: number, context?: DbContext): Promise { + async getProjectApiKeys( + projectId: number, + context?: DbContext, + ): Promise { const result = await context!.query({ text: queries.getProjectApiKeys, values: [projectId], @@ -433,7 +465,7 @@ export class ProjectRepository { text: queries.updateProjectUserPermissions, values: [projectId, userId, flags], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update project user permissions', + validationErrorMessage: "Failed to update project user permissions", }); } @@ -463,13 +495,16 @@ export class ProjectRepository { } @withDbContext - async inviteUserToProject(data: InviteUserToProjectDto, context?: DbContext): Promise { + async inviteUserToProject( + data: InviteUserToProjectDto, + context?: DbContext, + ): Promise { const flags = this.permissionsToFlags(data.permissions); await context!.query({ text: queries.inviteUserToProject, values: [data.projectId, data.emailAddress, flags], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to invite user to project', + validationErrorMessage: "Failed to invite user to project", }); } @@ -494,7 +529,7 @@ export class ProjectRepository { text: queries.cancelProjectInvitation, values: [projectId, emailAddress], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to cancel project invitation', + validationErrorMessage: "Failed to cancel project invitation", }); } @@ -508,7 +543,7 @@ export class ProjectRepository { text: queries.acceptProjectInvitation, values: [projectId, emailAddress], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to accept invitation', + validationErrorMessage: "Failed to accept invitation", }); } @@ -522,7 +557,7 @@ export class ProjectRepository { text: queries.removeUserFromProject, values: [projectId, userId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to remove user from project', + validationErrorMessage: "Failed to remove user from project", }); } @@ -576,7 +611,7 @@ export class ProjectRepository { text: queries.trackUsage, values: [amount, subscriptionId, resource], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to track usage', + validationErrorMessage: "Failed to track usage", }); } @@ -591,12 +626,15 @@ export class ProjectRepository { text: queries.setUsage, values: [amount, subscriptionId, resource], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to track usage', + validationErrorMessage: "Failed to track usage", }); } @withDbContext - async getPlanSummaryById(planId: number, context: DbContext): Promise { + async getPlanSummaryById( + planId: number, + context: DbContext, + ): Promise { const result = await context.query({ text: queries.getPlanById, values: [planId], @@ -618,7 +656,10 @@ export class ProjectRepository { } @withDbContext - async getPlanById(planId: number, context?: DbContext): Promise { + async getPlanById( + planId: number, + context?: DbContext, + ): Promise { const planResult = await context!.query({ text: queries.getPlanById, values: [planId], @@ -716,13 +757,13 @@ export class ProjectRepository { text: queries.updateProjectCollection, values: [projectId, collectionId, name, uri, isPublic], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to update project collection', + validationErrorMessage: "Failed to update project collection", }); context?.commitTransaction(); return queryResult.rows[0].id; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to update project collection', error); + this.logger.error("Failed to update project collection", error); throw error; } } @@ -739,12 +780,12 @@ export class ProjectRepository { text: queries.deleteProjectCollection, values: [projectId, collectionId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to delete project collection', + validationErrorMessage: "Failed to delete project collection", }); context!.commitTransaction(); } catch (error) { context!.rollbackTransaction(); - this.logger.error('Failed to delete project collection', error); + this.logger.error("Failed to delete project collection", error); throw error; } } @@ -763,13 +804,13 @@ export class ProjectRepository { text: queries.addProjectCollection, values: [projectId, name, uri, isPublic], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to add project collection', + validationErrorMessage: "Failed to add project collection", }); context?.commitTransaction(); return queryResult.rows[0].id; } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to create project collection', error); + this.logger.error("Failed to create project collection", error); throw error; } } @@ -806,12 +847,12 @@ export class ProjectRepository { text: queries.addUserToProject, values: [projectId, userId, permissionsFlags], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'Failed to add user to project', + validationErrorMessage: "Failed to add user to project", }); context?.commitTransaction(); } catch (error) { context?.rollbackTransaction(); - this.logger.error('Failed to add project', error); + this.logger.error("Failed to add project", error); throw error; } } @@ -826,7 +867,7 @@ export class ProjectRepository { text: queries.getInvitationPermissionFlags, values: [emailAddress, projectId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'You are not invited to this project', + validationErrorMessage: "You are not invited to this project", }); return pernissiondDbResult.rows[0].permission_flags; } diff --git a/src/modules/repositories/projects/queries.ts b/src/modules/repositories/projects/queries.ts index d345c66..87e7843 100644 --- a/src/modules/repositories/projects/queries.ts +++ b/src/modules/repositories/projects/queries.ts @@ -1,7 +1,8 @@ -import { SubscriptionStatus } from '../../shared/types/subscriptions'; +import { SubscriptionStatus } from "../../shared/types/subscriptions"; export const queries = { - createProject: 'INSERT INTO "bonadocs"."projects" (slug, name) VALUES ($1, $2) RETURNING id;', + createProject: + 'INSERT INTO "bonadocs"."projects" (slug, name) VALUES ($1, $2) RETURNING id;', deleteProject: 'DELETE FROM "bonadocs"."projects" WHERE id = $1;', getProjectById: 'SELECT p.id, p.slug, p.name, p.date_created FROM "bonadocs"."projects" p WHERE p.id = $1 LIMIT 1;', @@ -9,7 +10,8 @@ export const queries = { 'SELECT p.id, p.slug, p.name, p.date_created FROM "bonadocs"."projects" p WHERE p.slug = $1 LIMIT 1;', getProjectByApiKeyHash: 'SELECT p.id, p.slug, p.name, p.date_created FROM "bonadocs"."api_keys" ak JOIN "bonadocs"."projects" p ON ak.project_id = p.id WHERE ak.encrypted_key = $1 AND ak.date_expires > NOW() LIMIT 1;', - revokeApiKey: 'DELETE FROM "bonadocs"."api_keys" WHERE id = $1 AND project_id = $2;', + revokeApiKey: + 'DELETE FROM "bonadocs"."api_keys" WHERE id = $1 AND project_id = $2;', getProjectApiKeys: 'SELECT id, name, date_created, date_expires FROM "bonadocs"."api_keys" WHERE project_id = $1;', addProjectApiKey: @@ -56,7 +58,8 @@ export const queries = { 'SELECT c.id, c.name, c.uri, c.is_public, c.last_updated FROM "bonadocs"."collections" c WHERE c.project_id = $1 AND c.id = $2 LIMIT 1', updateProjectCollection: 'UPDATE "bonadocs"."collections" SET name = $3, uri = $4, is_public = $5, last_updated = now() WHERE project_id = $1 AND id = $2 RETURNING id', - deleteProjectCollection: 'DELETE FROM "bonadocs"."collections" WHERE project_id = $1 AND id = $2', + deleteProjectCollection: + 'DELETE FROM "bonadocs"."collections" WHERE project_id = $1 AND id = $2', getActiveSubscriptionsForProject: ` SELECT s.id, s.project_id, @@ -130,7 +133,8 @@ export const queries = { ON s.id = sr.subscription_id AND s.date_expires > $1 AND s.date_expires < $2`, - updateProjectMetadata: 'UPDATE "bonadocs"."projects" SET metadata = $2 WHERE id = $1', + updateProjectMetadata: + 'UPDATE "bonadocs"."projects" SET metadata = $2 WHERE id = $1', checkIfProjectExistById: 'SELECT EXISTS ( SELECT 1 FROM "bonadocs"."projects" p WHERE p.id = $1 ) AS project_exists;', checkIfCollectionExistById: diff --git a/src/modules/repositories/projects/types.ts b/src/modules/repositories/projects/types.ts index 25b0118..fd360f9 100644 --- a/src/modules/repositories/projects/types.ts +++ b/src/modules/repositories/projects/types.ts @@ -1,8 +1,10 @@ -import { SubscriptionResource, SubscriptionStatus } from '../../shared/types/subscriptions'; -import { UserData } from '../users/types'; +import { + SubscriptionResource, + SubscriptionStatus, +} from "../../shared/types/subscriptions"; +import { UserData } from "../users/types"; -// eslint-disable-next-line import/no-cycle -import { PermissionName } from './util'; +import { PermissionName } from "./util"; export interface CreateProjectDto { creatorId: number; diff --git a/src/modules/repositories/projects/util.ts b/src/modules/repositories/projects/util.ts index aff4749..0f88e3a 100644 --- a/src/modules/repositories/projects/util.ts +++ b/src/modules/repositories/projects/util.ts @@ -1,7 +1,6 @@ -import { QueryResult } from 'pg'; +import { QueryResult } from "pg"; -// eslint-disable-next-line import/no-cycle -import { SubscriptionDTO } from './types'; +import { SubscriptionDTO } from "./types"; export const ProjectPermissions = { admin: 1, @@ -13,7 +12,9 @@ export const ProjectPermissions = { }; export type PermissionName = keyof typeof ProjectPermissions; -export const PermissionNames = Object.keys(ProjectPermissions) as PermissionName[]; +export const PermissionNames = Object.keys( + ProjectPermissions, +) as PermissionName[]; export function flagsToPermissions(flags: number): PermissionName[] { const permissions: PermissionName[] = []; @@ -27,7 +28,10 @@ export function flagsToPermissions(flags: number): PermissionName[] { return permissions; } -export function hasPermission(flags: number, permission: PermissionName): boolean { +export function hasPermission( + flags: number, + permission: PermissionName, +): boolean { const requiredPermission = ProjectPermissions[permission]; const adminPermission = ProjectPermissions.admin; @@ -39,7 +43,9 @@ export function hasPermission(flags: number, permission: PermissionName): boolea ); } -export function convertSubscriptionResultSet(result: QueryResult): SubscriptionDTO[] { +export function convertSubscriptionResultSet( + result: QueryResult, +): SubscriptionDTO[] { const subscriptions = new Map(); for (const row of result.rows) { diff --git a/src/modules/repositories/sample/index.ts b/src/modules/repositories/sample/index.ts index 4669259..888ba5a 100644 --- a/src/modules/repositories/sample/index.ts +++ b/src/modules/repositories/sample/index.ts @@ -1,2 +1,2 @@ -export { SampleRepository } from './sample.repository'; -export * from './types'; +export { SampleRepository } from "./sample.repository"; +export * from "./types"; diff --git a/src/modules/repositories/sample/sample.repository.test.ts b/src/modules/repositories/sample/sample.repository.test.ts index 97039fb..c4bc616 100644 --- a/src/modules/repositories/sample/sample.repository.test.ts +++ b/src/modules/repositories/sample/sample.repository.test.ts @@ -1,12 +1,12 @@ -import { beforeEach, describe, expect, test } from '@jest/globals'; +import { beforeEach, describe, expect, test } from "@jest/globals"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { getMockLogger } from '../../../test/util'; +import { getMockLogger } from "../../../test/util"; -import { SampleRepository } from './sample.repository'; +import { SampleRepository } from "./sample.repository"; -describe('DisbursementRepository', () => { +describe("DisbursementRepository", () => { let disbursementRepository: SampleRepository; let logger: BonadocsLogger; @@ -15,10 +15,10 @@ describe('DisbursementRepository', () => { disbursementRepository = new SampleRepository(logger); }); - describe('getSampleById', () => { - test('should return the sample if a matching id exists', async () => { + describe("getSampleById", () => { + test("should return the sample if a matching id exists", async () => { // arrange - const sample = await disbursementRepository.createSample('test-sample'); + const sample = await disbursementRepository.createSample("test-sample"); // act const result = await disbursementRepository.getSampleById(sample.id); @@ -28,7 +28,7 @@ describe('DisbursementRepository', () => { expect(result!.id).toEqual(sample.id); }); - test('should return null if a matching id does not exist', async () => { + test("should return null if a matching id does not exist", async () => { // act const result = await disbursementRepository.getSampleById(100); @@ -37,15 +37,15 @@ describe('DisbursementRepository', () => { }); }); - describe('createSample', () => { - test('should create a sample and return it', async () => { + describe("createSample", () => { + test("should create a sample and return it", async () => { // act - const result = await disbursementRepository.createSample('test-sample'); + const result = await disbursementRepository.createSample("test-sample"); // assert expect(result).not.toBeNull(); expect(result.id).toEqual(1); - expect(result.name).toEqual('test-sample'); + expect(result.name).toEqual("test-sample"); }); }); }); diff --git a/src/modules/repositories/sample/sample.repository.ts b/src/modules/repositories/sample/sample.repository.ts index 0594579..32952af 100644 --- a/src/modules/repositories/sample/sample.repository.ts +++ b/src/modules/repositories/sample/sample.repository.ts @@ -1,9 +1,9 @@ -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { SampleData } from './types'; +import { SampleData } from "./types"; /** * This is to demonstrate the use of repositories in your projects @@ -12,15 +12,17 @@ import { SampleData } from './types'; export class SampleRepository { private readonly store = new Map(); - constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} + constructor( + @Inject(diConstants.logger) private readonly logger: BonadocsLogger, + ) {} async getSampleById(id: number): Promise { - this.logger.info('Fetching sample data', { id }); + this.logger.info("Fetching sample data", { id }); return this.store.get(id) ?? null; } async createSample(name: string): Promise { - this.logger.info('Creating sample data', { name }); + this.logger.info("Creating sample data", { name }); const id = this.store.size + 1; this.store.set(id, { id, name }); return { id, name }; diff --git a/src/modules/repositories/users/index.ts b/src/modules/repositories/users/index.ts index c2706da..da8c0dd 100644 --- a/src/modules/repositories/users/index.ts +++ b/src/modules/repositories/users/index.ts @@ -1,2 +1,2 @@ -export { UserRepository } from './user.repository'; -export * from './types'; +export { UserRepository } from "./user.repository"; +export * from "./types"; diff --git a/src/modules/repositories/users/queries.ts b/src/modules/repositories/users/queries.ts index a5c451a..c388e38 100644 --- a/src/modules/repositories/users/queries.ts +++ b/src/modules/repositories/users/queries.ts @@ -32,9 +32,12 @@ export const queries = { WHERE u.email_address = $1 AND u.is_blacklisted = false LIMIT 1;`, - getUserMetadata: 'SELECT key, value FROM "bonadocs"."user_metadata" WHERE user_id = $1;', + getUserMetadata: + 'SELECT key, value FROM "bonadocs"."user_metadata" WHERE user_id = $1;', - updateUserProfile: 'UPDATE "bonadocs"."users" SET first_name = $1, last_name = $2 WHERE id = $3;', + updateUserProfile: + 'UPDATE "bonadocs"."users" SET first_name = $1, last_name = $2 WHERE id = $3;', - getUserBlacklistStatus: 'SELECT is_blacklisted FROM "bonadocs"."users" WHERE id = $1;', + getUserBlacklistStatus: + 'SELECT is_blacklisted FROM "bonadocs"."users" WHERE id = $1;', }; diff --git a/src/modules/repositories/users/user.repository.test.ts b/src/modules/repositories/users/user.repository.test.ts index 05e6b23..de17d50 100644 --- a/src/modules/repositories/users/user.repository.test.ts +++ b/src/modules/repositories/users/user.repository.test.ts @@ -1,17 +1,17 @@ -import { beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { getMockDbContext, getMockLogger } from '../../../test/util'; -import { DbContext } from '../../connection/dbcontext'; +import { getMockDbContext, getMockLogger } from "../../../test/util"; +import { DbContext } from "../../connection/dbcontext"; -import { CreateUserDto, UpdateUserDto, UserData } from './types'; -import { UserRepository } from './user.repository'; +import { CreateUserDto, UpdateUserDto, UserData } from "./types"; +import { UserRepository } from "./user.repository"; -jest.mock('../../connection/dbcontext'); -jest.mock('@bonadocs/logger'); +jest.mock("../../connection/dbcontext"); +jest.mock("@bonadocs/logger"); -describe('UserRepository', () => { +describe("UserRepository", () => { let userRepository: UserRepository; let context: jest.Mocked; let logger: jest.Mocked; @@ -22,56 +22,56 @@ describe('UserRepository', () => { context = getMockDbContext(); }); - describe('isUserBlacklisted', () => { - it('should return true if user is not found', async () => { + describe("isUserBlacklisted", () => { + it("should return true if user is not found", async () => { context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await userRepository.isUserBlacklisted(1, context); expect(true).toBe(result); }); - it('should return blacklist status if user is found and is_blacklisted is true', async () => { + it("should return blacklist status if user is found and is_blacklisted is true", async () => { // Mock query to return no rows context.query.mockResolvedValue({ rowCount: 1, rows: [{ is_blacklisted: true }], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await userRepository.isUserBlacklisted(1, context); expect(result).toBe(true); }); - it('should return blacklist status if user is found and is_blacklisted is false', async () => { + it("should return blacklist status if user is found and is_blacklisted is false", async () => { // Mock query to return no rows context.query.mockResolvedValue({ rowCount: 1, rows: [{ is_blacklisted: false }], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await userRepository.isUserBlacklisted(1, context); expect(result).toBe(false); }); }); - describe('createUser', () => { - it('should create a user and return userId', async () => { + describe("createUser", () => { + it("should create a user and return userId", async () => { const data: CreateUserDto = { - username: 'testuser', - emailAddress: 'test@example.com', - firstName: 'Test', - lastName: 'User', - authSource: 'source', - authId: 'authId', - metadata: { key: 'value' }, + username: "testuser", + emailAddress: "test@example.com", + firstName: "Test", + lastName: "User", + authSource: "source", + authId: "authId", + metadata: { key: "value" }, }; // mock insertUser query @@ -80,66 +80,68 @@ describe('UserRepository', () => { rows: [{ id: 1 }], oid: 0, fields: [], - command: 'INSERT', + command: "INSERT", }); const result = await userRepository.createUser(data, context); expect(result).toBe(1); }); - it('should rollback transaction if an error occurs', async () => { + it("should rollback transaction if an error occurs", async () => { const data: CreateUserDto = { - username: 'testuser', - emailAddress: 'test@example.com', - firstName: 'Test', - lastName: 'User', - authSource: 'source', - authId: 'authId', - metadata: { key: 'value' }, + username: "testuser", + emailAddress: "test@example.com", + firstName: "Test", + lastName: "User", + authSource: "source", + authId: "authId", + metadata: { key: "value" }, }; - context.query.mockRejectedValue(new Error('Insert user failed')); + context.query.mockRejectedValue(new Error("Insert user failed")); - await expect(userRepository.createUser(data, context)).rejects.toThrow('Insert user failed'); + await expect(userRepository.createUser(data, context)).rejects.toThrow( + "Insert user failed", + ); expect(context.rollbackTransaction).toHaveBeenCalled(); }); }); - describe('findUserById', () => { - it('should return user data if user is found', async () => { + describe("findUserById", () => { + it("should return user data if user is found", async () => { const userData = { id: 1, - username: 'testuser', - emailAddress: 'test@example.com', - firstName: 'Test', - lastName: 'User', + username: "testuser", + emailAddress: "test@example.com", + firstName: "Test", + lastName: "User", }; context.query.mockResolvedValue({ rowCount: 1, rows: [ { id: 1, - username: 'testuser', - email_address: 'test@example.com', - first_name: 'Test', - last_name: 'User', + username: "testuser", + email_address: "test@example.com", + first_name: "Test", + last_name: "User", }, ], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await userRepository.findUserById(1, context); expect(result).toEqual(userData); }); - it('should return null if user is not found', async () => { + it("should return null if user is not found", async () => { context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, fields: [], - command: 'SELECT', + command: "SELECT", }); const result = await userRepository.findUserById(1, context); @@ -147,14 +149,14 @@ describe('UserRepository', () => { }); }); - describe('findUserByUsername', () => { - it('should return user data if user is found', async () => { + describe("findUserByUsername", () => { + it("should return user data if user is found", async () => { const userData: UserData = { id: 1, - username: 'testuser', - emailAddress: 'test@example.com', - firstName: 'Test', - lastName: 'User', + username: "testuser", + emailAddress: "test@example.com", + firstName: "Test", + lastName: "User", }; context.query.mockResolvedValue({ @@ -162,43 +164,49 @@ describe('UserRepository', () => { rows: [ { id: 1, - username: 'testuser', - email_address: 'test@example.com', - first_name: 'Test', - last_name: 'User', + username: "testuser", + email_address: "test@example.com", + first_name: "Test", + last_name: "User", }, ], oid: 0, - command: 'SELECT', + command: "SELECT", fields: [], }); - const result = await userRepository.findUserByUsername('testuser', context); + const result = await userRepository.findUserByUsername( + "testuser", + context, + ); expect(result).toEqual(userData); }); - it('should return null if user is not found', async () => { + it("should return null if user is not found", async () => { context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, - command: 'SELECT', + command: "SELECT", fields: [], }); - const result = await userRepository.findUserByUsername('testuser', context); + const result = await userRepository.findUserByUsername( + "testuser", + context, + ); expect(result).toBeNull(); }); }); - describe('findUserByEmailAddress', () => { - it('should return user data if user is found', async () => { + describe("findUserByEmailAddress", () => { + it("should return user data if user is found", async () => { const userData: UserData = { id: 1, - username: 'testuser', - emailAddress: 'test@example.com', - firstName: 'Test', - lastName: 'User', + username: "testuser", + emailAddress: "test@example.com", + firstName: "Test", + lastName: "User", }; context.query.mockResolvedValue({ @@ -206,43 +214,49 @@ describe('UserRepository', () => { rows: [ { id: 1, - username: 'testuser', - email_address: 'test@example.com', - first_name: 'Test', - last_name: 'User', + username: "testuser", + email_address: "test@example.com", + first_name: "Test", + last_name: "User", }, ], oid: 0, - command: 'SELECT', + command: "SELECT", fields: [], }); - const result = await userRepository.findUserByEmailAddress('test@example.com', context); + const result = await userRepository.findUserByEmailAddress( + "test@example.com", + context, + ); expect(result).toEqual(userData); }); - it('should return null if user is not found', async () => { + it("should return null if user is not found", async () => { context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, - command: 'SELECT', + command: "SELECT", fields: [], }); - const result = await userRepository.findUserByEmailAddress('test@example.com', context); + const result = await userRepository.findUserByEmailAddress( + "test@example.com", + context, + ); expect(result).toBeNull(); }); }); - describe('findUserByAuth', () => { - it('should return user data if user is found', async () => { + describe("findUserByAuth", () => { + it("should return user data if user is found", async () => { const userData: UserData = { id: 1, - username: 'testuser', - emailAddress: 'test@example.com', - firstName: 'Test', - lastName: 'User', + username: "testuser", + emailAddress: "test@example.com", + firstName: "Test", + lastName: "User", }; context.query.mockResolvedValue({ @@ -250,49 +264,57 @@ describe('UserRepository', () => { rows: [ { id: 1, - username: 'testuser', - email_address: 'test@example.com', - first_name: 'Test', - last_name: 'User', + username: "testuser", + email_address: "test@example.com", + first_name: "Test", + last_name: "User", }, ], oid: 0, - command: 'SELECT', + command: "SELECT", fields: [], }); - const result = await userRepository.findUserByAuth('google', '12345', context); + const result = await userRepository.findUserByAuth( + "google", + "12345", + context, + ); expect(result).toEqual(userData); }); - it('should return null if user is not found', async () => { + it("should return null if user is not found", async () => { context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, - command: 'SELECT', + command: "SELECT", fields: [], }); - const result = await userRepository.findUserByAuth('google', '123456', context); + const result = await userRepository.findUserByAuth( + "google", + "123456", + context, + ); expect(result).toBeNull(); }); }); - describe('getUserMetadata', () => { - it('should return user metadata if found', async () => { - const metadata = { country: 'nigeria' }; + describe("getUserMetadata", () => { + it("should return user metadata if found", async () => { + const metadata = { country: "nigeria" }; context.query.mockResolvedValue({ rowCount: 1, rows: [ { - key: 'country', - value: 'nigeria', + key: "country", + value: "nigeria", }, ], oid: 0, - command: 'SELECT', + command: "SELECT", fields: [], }); @@ -300,12 +322,12 @@ describe('UserRepository', () => { expect(result).toEqual(metadata); }); - it('should return empty object if metadata is not found', async () => { + it("should return empty object if metadata is not found", async () => { context.query.mockResolvedValue({ rowCount: 0, rows: [], oid: 0, - command: 'SELECT', + command: "SELECT", fields: [], }); @@ -314,58 +336,68 @@ describe('UserRepository', () => { }); }); - describe('updateUserMetadata', () => { - it('should return true if metadata is updated', async () => { + describe("updateUserMetadata", () => { + it("should return true if metadata is updated", async () => { context.query.mockResolvedValue({ rowCount: 1, oid: 0, - command: 'UPDATE', + command: "UPDATE", fields: [], rows: [], }); - const result = await userRepository.updateUserMetadata(1, 'country', 'Nigeria', context); + const result = await userRepository.updateUserMetadata( + 1, + "country", + "Nigeria", + context, + ); expect(result).toBe(true); }); - it('should return false if metadata is not updated', async () => { + it("should return false if metadata is not updated", async () => { context.query.mockResolvedValue({ rowCount: 0, oid: 0, - command: 'UPDATE', + command: "UPDATE", fields: [], rows: [], }); - const result = await userRepository.updateUserMetadata(1, 'country', 'Nigeria', context); + const result = await userRepository.updateUserMetadata( + 1, + "country", + "Nigeria", + context, + ); expect(result).toBe(false); }); }); - describe('bulkUpdateUserMetadata', () => { - it('should return true if all metadata is updated', async () => { + describe("bulkUpdateUserMetadata", () => { + it("should return true if all metadata is updated", async () => { context.query.mockResolvedValue({ rowCount: 2, oid: 0, - command: 'UPDATE', + command: "UPDATE", fields: [], rows: [], }); const result = await userRepository.bulkUpdateUserMetadata( 1, - { country: 'Nigeria', state: 'Lagos' }, + { country: "Nigeria", state: "Lagos" }, context, ); expect(result).toBe(true); expect(context.commitTransaction).toHaveBeenCalled(); }); - it('should return false if any metadata update fails', async () => { - context.query.mockRejectedValue(new Error('Update failed')); + it("should return false if any metadata update fails", async () => { + context.query.mockRejectedValue(new Error("Update failed")); const result = await userRepository.bulkUpdateUserMetadata( 1, - { country: 'Nigeria', state: 'Lagos' }, + { country: "Nigeria", state: "Lagos" }, context, ); expect(result).toBe(false); @@ -373,17 +405,17 @@ describe('UserRepository', () => { }); }); - describe('updateUserProfile', () => { - it('should return true if profile is updated', async () => { + describe("updateUserProfile", () => { + it("should return true if profile is updated", async () => { const data: UpdateUserDto = { id: 1, - firstName: 'Updated', - lastName: 'User', + firstName: "Updated", + lastName: "User", }; context.query.mockResolvedValue({ rowCount: 1, oid: 0, - command: 'UPDATE', + command: "UPDATE", fields: [], rows: [], }); @@ -391,17 +423,17 @@ describe('UserRepository', () => { expect(result).toBe(true); }); - it('should return false if profile is not updated', async () => { + it("should return false if profile is not updated", async () => { const data: UpdateUserDto = { id: 1, - firstName: 'Updated', - lastName: 'User', + firstName: "Updated", + lastName: "User", }; context.query.mockResolvedValue({ rowCount: 0, oid: 0, - command: 'UPDATE', + command: "UPDATE", fields: [], rows: [], }); diff --git a/src/modules/repositories/users/user.repository.ts b/src/modules/repositories/users/user.repository.ts index 5edb4fc..4055ca7 100644 --- a/src/modules/repositories/users/user.repository.ts +++ b/src/modules/repositories/users/user.repository.ts @@ -1,17 +1,19 @@ -import { QueryResult } from 'pg'; -import { Inject, Service } from 'typedi'; +import { QueryResult } from "pg"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { DbContext, withDbContext } from '../../connection/dbcontext'; +import { DbContext, withDbContext } from "../../connection/dbcontext"; -import { queries } from './queries'; -import { CreateUserDto, UpdateUserDto, UserData } from './types'; +import { queries } from "./queries"; +import { CreateUserDto, UpdateUserDto, UserData } from "./types"; @Service() export class UserRepository { - constructor(@Inject(diConstants.logger) private readonly logger: BonadocsLogger) {} + constructor( + @Inject(diConstants.logger) private readonly logger: BonadocsLogger, + ) {} @withDbContext async isUserBlacklisted(id: number, context?: DbContext): Promise { @@ -28,7 +30,10 @@ export class UserRepository { } @withDbContext - async createUser(data: CreateUserDto, context?: DbContext): Promise { + async createUser( + data: CreateUserDto, + context?: DbContext, + ): Promise { context = context!; try { @@ -36,9 +41,14 @@ export class UserRepository { await context.beginTransaction(); const userResult = await context.query({ text: queries.insertUser, - values: [data.username, data.emailAddress, data.firstName, data.lastName], + values: [ + data.username, + data.emailAddress, + data.firstName, + data.lastName, + ], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'User Creation Failed', + validationErrorMessage: "User Creation Failed", }); const userId = userResult.rows[0].id; @@ -47,7 +57,7 @@ export class UserRepository { text: queries.insertUserAuth, values: [userId, data.authSource, data.authId], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'User Auth Creation Failed', + validationErrorMessage: "User Auth Creation Failed", }); const metadataEntries = Object.entries(data.metadata); @@ -56,7 +66,7 @@ export class UserRepository { text: queries.insertOrUpdateUserMetadata, values: [userId, key, value], validateResult: (result) => !!result.rowCount, - validationErrorMessage: 'User Metadata Creation Failed', + validationErrorMessage: "User Metadata Creation Failed", }), ); @@ -71,7 +81,10 @@ export class UserRepository { } @withDbContext - async findUserById(id: number, context: DbContext | null = null): Promise { + async findUserById( + id: number, + context: DbContext | null = null, + ): Promise { const result = await context?.query({ text: queries.findUserByID, values: [id], @@ -85,7 +98,10 @@ export class UserRepository { } @withDbContext - async findUserByUsername(username: string, context: DbContext): Promise { + async findUserByUsername( + username: string, + context: DbContext, + ): Promise { const result = await context.query({ text: queries.findUserByUsername, values: [username], @@ -134,7 +150,10 @@ export class UserRepository { } @withDbContext - async getUserMetadata(userId: number, context: DbContext): Promise> { + async getUserMetadata( + userId: number, + context: DbContext, + ): Promise> { const result = await context.query({ text: queries.getUserMetadata, values: [userId], @@ -197,7 +216,10 @@ export class UserRepository { } @withDbContext - async updateUserProfile(data: UpdateUserDto, context: DbContext): Promise { + async updateUserProfile( + data: UpdateUserDto, + context: DbContext, + ): Promise { const result = await context.query({ text: queries.updateUserProfile, values: [data.firstName, data.lastName, data.id], diff --git a/src/modules/shared/batching.ts b/src/modules/shared/batching.ts index 4dcb85d..5216ff5 100644 --- a/src/modules/shared/batching.ts +++ b/src/modules/shared/batching.ts @@ -1,7 +1,7 @@ -import { ContainerInstance } from 'typedi'; +import { ContainerInstance } from "typedi"; -import { diConstants, runInScope } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants, runInScope } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; export async function streamProcess( data: AsyncIterable | Iterable, @@ -9,7 +9,10 @@ export async function streamProcess( process: (it: TData) => TResult | Promise, ): Promise { const result: TResult[] = []; - const iter = Symbol.iterator in data ? data[Symbol.iterator]() : data[Symbol.asyncIterator](); + const iter = + Symbol.iterator in data + ? data[Symbol.iterator]() + : data[Symbol.asyncIterator](); const workers = Array.from({ length: concurrency }, async () => { let item = await iter.next(); while (!item.done) { @@ -28,7 +31,10 @@ export async function streamJobProcess( data: AsyncIterable | Iterable, concurrency: number, logger: BonadocsLogger, - process: (container: ContainerInstance, it: TData) => TResult | Promise, + process: ( + container: ContainerInstance, + it: TData, + ) => TResult | Promise, ): Promise { await streamProcess(data, concurrency, (it) => runInScope(async (container) => { diff --git a/src/modules/shared/index.ts b/src/modules/shared/index.ts index 307868f..e45f6a6 100644 --- a/src/modules/shared/index.ts +++ b/src/modules/shared/index.ts @@ -1,6 +1,6 @@ -export * from './types'; -export * from './util'; -export * from './batching'; +export * from "./types"; +export * from "./util"; +export * from "./batching"; /** * Type for all responses returned by JSON controller actions. Controller @@ -8,7 +8,7 @@ export * from './batching'; * or raw buffers should not use this type. */ export type JsonResponse = { - status: 'successful' | 'error'; + status: "successful" | "error"; message: string; data?: T; }; diff --git a/src/modules/shared/types/index.ts b/src/modules/shared/types/index.ts index 9ca17a4..9c6e5f4 100644 --- a/src/modules/shared/types/index.ts +++ b/src/modules/shared/types/index.ts @@ -1 +1 @@ -export type { DisbursementMeta } from './disbursementmeta'; +export type { DisbursementMeta } from "./disbursementmeta"; diff --git a/src/modules/shared/util.test.ts b/src/modules/shared/util.test.ts index ac34335..cfa110c 100644 --- a/src/modules/shared/util.test.ts +++ b/src/modules/shared/util.test.ts @@ -1,10 +1,10 @@ -import { describe, expect, jest, test } from '@jest/globals'; +import { describe, expect, jest, test } from "@jest/globals"; -import { lazy } from './util'; +import { lazy } from "./util"; -describe('shared/util', () => { - describe('lazy', () => { - test('should call the factory function only once', () => { +describe("shared/util", () => { + describe("lazy", () => { + test("should call the factory function only once", () => { // arrange const factory = jest.fn(); const lazyValue = lazy(factory); @@ -17,16 +17,16 @@ describe('shared/util', () => { expect(factory).toHaveBeenCalledTimes(1); }); - test('should return the value from the factory function', () => { + test("should return the value from the factory function", () => { // arrange - const factory = jest.fn(() => 'value'); + const factory = jest.fn(() => "value"); const lazyValue = lazy(factory); // act const result = lazyValue(); // assert - expect(result).toBe('value'); + expect(result).toBe("value"); }); }); }); diff --git a/src/modules/shared/util.ts b/src/modules/shared/util.ts index 7aac5e3..15e4c2d 100644 --- a/src/modules/shared/util.ts +++ b/src/modules/shared/util.ts @@ -22,7 +22,10 @@ export function lazy(fn: () => T): () => T { * @param value * @param message */ -export function assertNotNull(value: T | undefined | null, message: string): asserts value is T { +export function assertNotNull( + value: T | undefined | null, + message: string, +): asserts value is T { if (value == null) { throw new Error(message); } diff --git a/src/modules/storage/index.ts b/src/modules/storage/index.ts index 3b0f140..7c261f0 100644 --- a/src/modules/storage/index.ts +++ b/src/modules/storage/index.ts @@ -1,11 +1,11 @@ -import { Storage as GCloudStorage } from '@google-cloud/storage'; -import { Inject, Service } from 'typedi'; +import { Storage as GCloudStorage } from "@google-cloud/storage"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { GCP } from '../configuration/config.interface'; -import { ConfigService } from '../configuration/config.service'; +import { GCP } from "../configuration/config.interface"; +import { ConfigService } from "../configuration/config.service"; let storageClient: GCloudStorage | undefined; @@ -20,7 +20,7 @@ export class Storage { @Inject(diConstants.logger) private readonly logger: BonadocsLogger, @Inject() private readonly configService: ConfigService, ) { - this.gcpProjectId = this.configService.getTransformed('gcp').projectId; + this.gcpProjectId = this.configService.getTransformed("gcp").projectId; } async uploadFile( @@ -39,8 +39,8 @@ export class Storage { public: isPublic, }); } catch (e) { - this.logger.error('Error uploading file to GCP', e, { bucket, fileName }); - throw new Error('File upload failed'); + this.logger.error("Error uploading file to GCP", e, { bucket, fileName }); + throw new Error("File upload failed"); } } @@ -50,7 +50,11 @@ export class Storage { return file.publicUrl(); } - async configureFileAccess(bucket: string, fileName: string, isPublic: boolean): Promise { + async configureFileAccess( + bucket: string, + fileName: string, + isPublic: boolean, + ): Promise { const storage = getStorageClient(this.gcpProjectId); try { @@ -63,16 +67,19 @@ export class Storage { await file.makePrivate(); } } catch (e) { - this.logger.error('Error configuring file access in GCP', e, { + this.logger.error("Error configuring file access in GCP", e, { bucket, fileName, isPublic, }); - throw new Error('File access configuration failed'); + throw new Error("File access configuration failed"); } } - async downloadFile(bucket: string, fileName: string): Promise { + async downloadFile( + bucket: string, + fileName: string, + ): Promise { const storage = getStorageClient(this.gcpProjectId); try { @@ -80,9 +87,9 @@ export class Storage { const file = bucketRef.file(fileName); const [data] = await file.download(); - return data.toString('utf8'); + return data.toString("utf8"); } catch (e) { - this.logger.error('Error downloading file from GCP', e, { + this.logger.error("Error downloading file from GCP", e, { bucket, fileName, }); @@ -99,11 +106,11 @@ export class Storage { await file.delete(); } catch (e) { - this.logger.error('Error deleting file from GCP', e, { + this.logger.error("Error deleting file from GCP", e, { bucket, fileName, }); - throw new Error('File deletion failed'); + throw new Error("File deletion failed"); } } @@ -127,12 +134,12 @@ export class Storage { const [data] = await file.download(); return { name: file.name, - data: data.toString('utf8'), + data: data.toString("utf8"), }; }), ); } catch (e) { - this.logger.error('Error listing files from GCP', e, { + this.logger.error("Error listing files from GCP", e, { bucket, path: prefix, }); @@ -149,11 +156,11 @@ export class Storage { await Promise.all(files.map((file) => file.delete())); } catch (e) { - this.logger.error('Error deleting files from GCP', e, { + this.logger.error("Error deleting files from GCP", e, { bucket, path: prefix, }); - throw new Error('File deletion failed'); + throw new Error("File deletion failed"); } } } diff --git a/src/modules/subscription/index.ts b/src/modules/subscription/index.ts index 03f891e..b8b7920 100644 --- a/src/modules/subscription/index.ts +++ b/src/modules/subscription/index.ts @@ -1 +1 @@ -export { SubscriptionController } from './subscription.controller'; +export { SubscriptionController } from "./subscription.controller"; diff --git a/src/modules/subscription/subscription.controller.ts b/src/modules/subscription/subscription.controller.ts index be11e80..cb1e01e 100644 --- a/src/modules/subscription/subscription.controller.ts +++ b/src/modules/subscription/subscription.controller.ts @@ -1,24 +1,30 @@ -import { Request } from 'express'; -import { Get, JsonController, Req } from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +import { Request } from "express"; +import { Get, JsonController, Req } from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { JsonResponse } from '../shared'; +import { JsonResponse } from "../shared"; -import { GetProjectSubscriptionResponse } from './subscription.interface'; -import { SubscriptionService } from './subscription.service'; +import { GetProjectSubscriptionResponse } from "./subscription.interface"; +import { SubscriptionService } from "./subscription.service"; @Service() -@JsonController('/subscriptions') +@JsonController("/subscriptions") export class SubscriptionController { - constructor(@Inject() private readonly subscriptionService: SubscriptionService) {} + constructor( + @Inject() private readonly subscriptionService: SubscriptionService, + ) {} - @Get('') - async list(@Req() request: Request): Promise> { - const response = await this.subscriptionService.getSubscription(request.auth.projectId!); + @Get("") + async list( + @Req() request: Request, + ): Promise> { + const response = await this.subscriptionService.getSubscription( + request.auth.projectId!, + ); return { data: response, - status: 'successful', - message: 'Subscription fetched successfully', + status: "successful", + message: "Subscription fetched successfully", }; } } diff --git a/src/modules/subscription/subscription.interface.ts b/src/modules/subscription/subscription.interface.ts index 6ba6742..9df1405 100644 --- a/src/modules/subscription/subscription.interface.ts +++ b/src/modules/subscription/subscription.interface.ts @@ -1,3 +1,3 @@ -import { SubscriptionDTO } from '../repositories/projects/types'; +import { SubscriptionDTO } from "../repositories/projects/types"; export type GetProjectSubscriptionResponse = SubscriptionDTO[]; diff --git a/src/modules/subscription/subscription.service.test.ts b/src/modules/subscription/subscription.service.test.ts index b00fe82..0c5b9e3 100644 --- a/src/modules/subscription/subscription.service.test.ts +++ b/src/modules/subscription/subscription.service.test.ts @@ -1,28 +1,28 @@ -import 'reflect-metadata'; +import "reflect-metadata"; -import { beforeEach, describe, expect, it, jest } from '@jest/globals'; +import { beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { BonadocsLogger } from '@bonadocs/logger'; +import { BonadocsLogger } from "@bonadocs/logger"; -import { generateMockObject, getMockLogger } from '../../test/util'; -import { ProjectRepository } from '../repositories/projects/project.repository'; -import { SubscriptionStatus } from '../shared/types/subscriptions'; +import { generateMockObject, getMockLogger } from "../../test/util"; +import { ProjectRepository } from "../repositories/projects/project.repository"; +import { SubscriptionStatus } from "../shared/types/subscriptions"; -import { SubscriptionService } from './subscription.service'; +import { SubscriptionService } from "./subscription.service"; -describe('SubscriptionService', () => { +describe("SubscriptionService", () => { let logger: jest.Mocked; let projectRepository: jest.Mocked; let subscriptionService: SubscriptionService; beforeEach(() => { logger = getMockLogger(); - projectRepository = generateMockObject('getSubscriptionsForProject'); + projectRepository = generateMockObject("getSubscriptionsForProject"); subscriptionService = new SubscriptionService(logger, projectRepository); }); - describe('get subscriptions', () => { - it('get subscription should be successful', async () => { + describe("get subscriptions", () => { + it("get subscription should be successful", async () => { // arrange projectRepository.getSubscriptionsForProject.mockResolvedValue([ { diff --git a/src/modules/subscription/subscription.service.ts b/src/modules/subscription/subscription.service.ts index 4177f7e..da00e60 100644 --- a/src/modules/subscription/subscription.service.ts +++ b/src/modules/subscription/subscription.service.ts @@ -1,11 +1,11 @@ -import { Inject, Service } from 'typedi'; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { ProjectRepository } from '../repositories/projects/project.repository'; +import { ProjectRepository } from "../repositories/projects/project.repository"; -import { GetProjectSubscriptionResponse } from './subscription.interface'; +import { GetProjectSubscriptionResponse } from "./subscription.interface"; @Service() export class SubscriptionService { @@ -14,10 +14,15 @@ export class SubscriptionService { @Inject() private readonly projectRepository: ProjectRepository, ) {} - async getSubscription(projectId: number): Promise { + async getSubscription( + projectId: number, + ): Promise { this.logger.info(`Getting subscription for project ${projectId}`); - const subscription = await this.projectRepository.getSubscriptionsForProject(projectId); - this.logger.info(`Subscription for project ${projectId} fetched successfully`); + const subscription = + await this.projectRepository.getSubscriptionsForProject(projectId); + this.logger.info( + `Subscription for project ${projectId} fetched successfully`, + ); return subscription; } } diff --git a/src/modules/tenderly/index.ts b/src/modules/tenderly/index.ts index 1b8443e..bac79f8 100644 --- a/src/modules/tenderly/index.ts +++ b/src/modules/tenderly/index.ts @@ -1 +1 @@ -export { TenderlyController } from './tenderly.controller'; +export { TenderlyController } from "./tenderly.controller"; diff --git a/src/modules/tenderly/tenderly.controller.ts b/src/modules/tenderly/tenderly.controller.ts index bbcb140..7000ddb 100644 --- a/src/modules/tenderly/tenderly.controller.ts +++ b/src/modules/tenderly/tenderly.controller.ts @@ -1,20 +1,20 @@ -import { Body, Post, JsonController, QueryParam } from 'routing-controllers'; -import { Inject, Service } from 'typedi'; +import { Body, Post, JsonController, QueryParam } from "routing-controllers"; +import { Inject, Service } from "typedi"; -import { SimulationResponseData } from '../http'; -import { JsonResponse } from '../shared'; +import { SimulationResponseData } from "../http"; +import { JsonResponse } from "../shared"; -import { SimulationsRequest } from './tenderly.interface'; -import { TenderlyService } from './tenderly.service'; +import { SimulationsRequest } from "./tenderly.interface"; +import { TenderlyService } from "./tenderly.service"; @Service() -@JsonController('/tenderly') +@JsonController("/tenderly") export class TenderlyController { constructor(@Inject() private readonly tenderlyService: TenderlyService) {} - @Post('/simulate') + @Post("/simulate") async list( - @QueryParam('chainId') chainId: number, + @QueryParam("chainId") chainId: number, @Body({ validate: true }) payload: SimulationsRequest, ): Promise> { const response = await this.tenderlyService.stimulate(chainId, payload); @@ -22,13 +22,13 @@ export class TenderlyController { const serializedResponse = JSON.parse( // eslint-disable-next-line no-confusing-arrow JSON.stringify(response, (_key, value) => - typeof value === 'bigint' ? value.toString() : value, + typeof value === "bigint" ? value.toString() : value, ), ); return { data: serializedResponse, - status: 'successful', - message: 'Transaction Stimulated Successfully', + status: "successful", + message: "Transaction Stimulated Successfully", }; } } diff --git a/src/modules/tenderly/tenderly.interface.ts b/src/modules/tenderly/tenderly.interface.ts index ea9ddf3..1c1d6cd 100644 --- a/src/modules/tenderly/tenderly.interface.ts +++ b/src/modules/tenderly/tenderly.interface.ts @@ -1,3 +1,3 @@ -import { EVMCall } from '../http'; +import { EVMCall } from "../http"; export type SimulationsRequest = EVMCall[]; diff --git a/src/modules/tenderly/tenderly.service.ts b/src/modules/tenderly/tenderly.service.ts index 66937d0..3e1dc3e 100644 --- a/src/modules/tenderly/tenderly.service.ts +++ b/src/modules/tenderly/tenderly.service.ts @@ -1,14 +1,14 @@ -import { dataLength, isAddress, isHexString } from 'ethers'; -import { Inject, Service } from 'typedi'; +import { dataLength, isAddress, isHexString } from "ethers"; +import { Inject, Service } from "typedi"; -import { diConstants } from '@bonadocs/di'; -import { BonadocsLogger } from '@bonadocs/logger'; +import { diConstants } from "@bonadocs/di"; +import { BonadocsLogger } from "@bonadocs/logger"; -import { supportedChains } from '../blockscan'; -import { ApplicationError, applicationErrorCodes } from '../errors'; -import { SimulationResponseData, TenderlyApiClient } from '../http'; +import { supportedChains } from "../blockscan"; +import { ApplicationError, applicationErrorCodes } from "../errors"; +import { SimulationResponseData, TenderlyApiClient } from "../http"; -import { SimulationsRequest } from './tenderly.interface'; +import { SimulationsRequest } from "./tenderly.interface"; @Service() export class TenderlyService { @@ -17,20 +17,23 @@ export class TenderlyService { @Inject() private readonly httpClient: TenderlyApiClient, ) {} - async stimulate(chainId: number, request: SimulationsRequest): Promise { + async stimulate( + chainId: number, + request: SimulationsRequest, + ): Promise { if (!supportedChains.has(chainId)) { throw new ApplicationError({ logger: this.logger, - message: 'Unsupported chain', + message: "Unsupported chain", errorCode: applicationErrorCodes.unsupportedChain, - userFriendlyMessage: 'The specified chain is not supported', + userFriendlyMessage: "The specified chain is not supported", }); } for (const data of request) { if (!isAddress(data?.to)) { throw new ApplicationError({ logger: this.logger, - message: 'Invalid to address', + message: "Invalid to address", errorCode: applicationErrorCodes.invalidEVMAddress, userFriendlyMessage: "The 'to' address is invalid", }); @@ -38,7 +41,7 @@ export class TenderlyService { if (!isAddress(data?.overrides.from)) { throw new ApplicationError({ logger: this.logger, - message: 'Invalid to address', + message: "Invalid to address", errorCode: applicationErrorCodes.invalidEVMAddress, userFriendlyMessage: "The 'to' address is invalid", }); @@ -46,9 +49,9 @@ export class TenderlyService { if (!isHexString(data.data) || dataLength(data.data) < 4) { throw new ApplicationError({ logger: this.logger, - message: 'Invalid transaction data', + message: "Invalid transaction data", errorCode: applicationErrorCodes.invalidTransactionData, - userFriendlyMessage: 'The transaction data is not valid', + userFriendlyMessage: "The transaction data is not valid", }); } if ( @@ -57,9 +60,9 @@ export class TenderlyService { ) { throw new ApplicationError({ logger: this.logger, - message: 'Invalid simulation accounts list', + message: "Invalid simulation accounts list", errorCode: applicationErrorCodes.invalidEVMAddress, - userFriendlyMessage: 'The simulation accounts list is invalid', + userFriendlyMessage: "The simulation accounts list is invalid", }); } } @@ -72,14 +75,14 @@ export class TenderlyService { if (response !== undefined && Array.isArray(response)) { return response; } - if ('receipt' in response!) { + if ("receipt" in response!) { return [response]; } throw new ApplicationError({ logger: this.logger, - message: 'Simulation failed', + message: "Simulation failed", errorCode: applicationErrorCodes.simulationFailed, - userFriendlyMessage: 'Failed to run your simulation', + userFriendlyMessage: "Failed to run your simulation", }); } } diff --git a/src/server.ts b/src/server.ts index 09d3ddb..de33d0a 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,53 +1,53 @@ -import 'reflect-metadata'; -import './env'; +import "reflect-metadata"; +import "./env"; -import { getGlobalLogger } from '@bonadocs/di'; +import { getGlobalLogger } from "@bonadocs/di"; -import app from './app'; -import { getConfigService } from './modules'; +import app from "./app"; +import { getConfigService } from "./modules"; // logger used outside of job and request execution flows const globalLogger = getGlobalLogger(); const configService = getConfigService(); -const PORT = configService.getTransformed('port') || 8080; +const PORT = configService.getTransformed("port") || 8080; // start the server const server = app.listen(PORT, () => { globalLogger.info({ msg: `Server started ⚡ on port ${PORT}`, - context: 'express-server', + context: "express-server", }); }); -process.on('SIGTERM', gracefulShutdown); -process.on('SIGINT', gracefulShutdown); +process.on("SIGTERM", gracefulShutdown); +process.on("SIGINT", gracefulShutdown); -process.on('unhandledRejection', (reason, promise) => { - globalLogger.error('Unhandled Rejection error', { - context: 'unhandledRejection', +process.on("unhandledRejection", (reason, promise) => { + globalLogger.error("Unhandled Rejection error", { + context: "unhandledRejection", reason, promise, }); }); -process.on('uncaughtException', (err, origin) => { - globalLogger.error('Uncaught Exception error', { - context: 'uncaughtException', +process.on("uncaughtException", (err, origin) => { + globalLogger.error("Uncaught Exception error", { + context: "uncaughtException", err, origin, }); }); -process.on('exit', (code) => { +process.on("exit", (code) => { globalLogger.info({ - msg: 'Process exited', - context: 'exit', + msg: "Process exited", + context: "exit", code, }); }); function gracefulShutdown() { - globalLogger.info('Gracefully shutting down'); + globalLogger.info("Gracefully shutting down"); server.close(); } diff --git a/src/test/util.ts b/src/test/util.ts index a07d0b1..09e26cc 100644 --- a/src/test/util.ts +++ b/src/test/util.ts @@ -1,12 +1,12 @@ -import { randomUUID } from 'crypto'; +import { randomUUID } from "crypto"; -import { jest } from '@jest/globals'; -import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; -import type { FunctionLike, UnknownFunction } from 'jest-mock'; +import { jest } from "@jest/globals"; +import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios"; +import type { FunctionLike, UnknownFunction } from "jest-mock"; -import type { BonadocsLogger } from '@bonadocs/logger'; +import type { BonadocsLogger } from "@bonadocs/logger"; -import { DbContext } from '../modules/connection/dbcontext'; +import { DbContext } from "../modules/connection/dbcontext"; /** * This file should only contain utilities used for tests. All tests should @@ -19,7 +19,7 @@ import { DbContext } from '../modules/connection/dbcontext'; */ export function getMockLogger(): jest.Mocked { return { - ...generateMockObject('info', 'warn', 'error', 'debug', 'addContext'), + ...generateMockObject("info", "warn", "error", "debug", "addContext"), context: {}, }; } @@ -29,7 +29,12 @@ export function getMockLogger(): jest.Mocked { */ export function getMockDbContext(): jest.Mocked { return { - ...generateMockObject('query', 'beginTransaction', 'commitTransaction', 'rollbackTransaction'), + ...generateMockObject( + "query", + "beginTransaction", + "commitTransaction", + "rollbackTransaction", + ), }; } @@ -37,7 +42,9 @@ export function getMockDbContext(): jest.Mocked { * A simple wrapper to cast a value to a jest.Mock * @param fn */ -export function asMock(fn: T): jest.Mock { +export function asMock( + fn: T, +): jest.Mock { return fn as unknown as jest.Mock; } @@ -69,7 +76,7 @@ export function generateMockObject( * Mock the `axios.create` function to return a mock axios instance */ export function mockAxiosCreate(): jest.Mocked { - const axiosMock = generateMockObject('request'); + const axiosMock = generateMockObject("request"); axiosMock.defaults = { headers: {} } as typeof axiosMock.defaults; asMock(axios.create).mockImplementation((config) => { if (config?.headers) { @@ -88,14 +95,21 @@ export function mockAxiosCreate(): jest.Mocked { * @param mock * @param substring */ -export function hasBeenCalledWithSubstring(mock: jest.Mock, substring: string): boolean { - return mock.mock.calls.some((args) => args.some((arg) => arg?.toString().includes(substring))); +export function hasBeenCalledWithSubstring( + mock: jest.Mock, + substring: string, +): boolean { + return mock.mock.calls.some((args) => + args.some((arg) => arg?.toString().includes(substring)), + ); } /** * Generate a unique object with random values */ -export function generateObject(overrides: Partial = {}): T { +export function generateObject( + overrides: Partial = {}, +): T { return { value: randomUUID(), ...overrides, @@ -123,7 +137,10 @@ export type AxiosRequestMap = Map>; * @param client * @param requestResponseMap See AxiosRequestMap */ -export function mockAxiosRequest(client: AxiosInstance, requestResponseMap: AxiosRequestMap): void { +export function mockAxiosRequest( + client: AxiosInstance, + requestResponseMap: AxiosRequestMap, +): void { const returnValueMappings: ReturnValueMapping[] = []; for (const [key, value] of requestResponseMap) { const defaults: Record = { @@ -148,7 +165,9 @@ export function mockAxiosRequest(client: AxiosInstance, requestResponseMap: Axio */ export function getHttpRequestLog(logger: BonadocsLogger): string { return JSON.stringify( - asMock(logger.info).mock.calls.find((c) => c[0]?.toString().includes('HTTP request')), + asMock(logger.info).mock.calls.find((c) => + c[0]?.toString().includes("HTTP request"), + ), ); } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 934aae9..5a05065 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,6 +1,6 @@ -import type { ContainerInstance } from 'typedi'; +import type { ContainerInstance } from "typedi"; -import type { BonadocsLogger } from '@bonadocs/logger'; +import type { BonadocsLogger } from "@bonadocs/logger"; declare global { namespace Express { @@ -17,7 +17,7 @@ declare global { userId?: number; } - type AuthType = 'unauthenticated' | 'internal' | 'partner'; + type AuthType = "unauthenticated" | "internal" | "partner"; type AuthData = | UnauthenticatedAuthData | InternalAuthData @@ -25,12 +25,12 @@ declare global { | LegacyInternalAuthData; interface UnauthenticatedAuthData { - authType: 'unauthenticated'; + authType: "unauthenticated"; isAuthenticated: false; } interface InternalAuthData extends AuthenticatedAuthData { - authType: 'internal'; + authType: "internal"; isAuthenticated: true; isInternal: true; serviceId: string; @@ -38,13 +38,13 @@ declare global { } interface LegacyInternalAuthData extends AuthenticatedAuthData { - authType: 'internal'; + authType: "internal"; isAuthenticated: true; isInternal: true; } interface PartnerAuthData extends AuthenticatedAuthData { - authType: 'partner'; + authType: "partner"; isAuthenticated: true; isInternal: false; isLiveMode: boolean;