diff --git a/package-lock.json b/package-lock.json index bf1f958..2508b31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,9 +53,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", - "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "dev": true, "engines": { "node": ">=6.9.0" @@ -107,12 +107,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", - "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dev": true, "dependencies": { - "@babel/types": "^7.25.0", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -233,13 +233,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", - "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dev": true, "dependencies": { "@babel/template": "^7.25.0", - "@babel/types": "^7.25.0" + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" @@ -332,12 +332,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", - "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", "dev": true, "dependencies": { - "@babel/types": "^7.25.2" + "@babel/types": "^7.25.6" }, "bin": { "parser": "bin/babel-parser.js" @@ -347,9 +347,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", - "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz", + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -373,16 +373,16 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", - "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", "dev": true, "dependencies": { "@babel/code-frame": "^7.24.7", - "@babel/generator": "^7.25.0", - "@babel/parser": "^7.25.3", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", "@babel/template": "^7.25.0", - "@babel/types": "^7.25.2", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -400,9 +400,9 @@ } }, "node_modules/@babel/types": { - "version": "7.25.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", - "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.24.8", @@ -414,9 +414,9 @@ } }, "node_modules/@codegenie/serverless-express": { - "version": "4.14.1", - "resolved": "https://registry.npmjs.org/@codegenie/serverless-express/-/serverless-express-4.14.1.tgz", - "integrity": "sha512-B90/1OmA9mf9bEJnplLj7FGf+N2v2ikB68c/9W9uXmCa4ep/V00ymCiivwGLyeuzQRW33tcj4+KxZ2utfmu39Q==", + "version": "4.15.0", + "resolved": "https://registry.npmjs.org/@codegenie/serverless-express/-/serverless-express-4.15.0.tgz", + "integrity": "sha512-adnKbnW1Tg5LAe0lcbyoRchu8G6+gLwP1rvgwfHvTbCwvBQNfhsgnzq4cKkLn7ZKn2sa4JZNis/Gn/2jWBWa4A==", "dev": true, "engines": { "node": ">=12" @@ -738,9 +738,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -792,9 +792,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -856,13 +856,13 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -938,9 +938,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", - "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "engines": { "node": ">=12" }, @@ -1526,17 +1526,17 @@ } }, "node_modules/@loopback/boot": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/boot/-/boot-7.0.4.tgz", - "integrity": "sha512-LWF2EOMu2U5uoJGGx6eA0fbvztWSmlmjaCag+xVbB2yAohW6QMwVXpjlk0WauGGlwmGwKfKSbGUScdFKl2VeEQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@loopback/boot/-/boot-7.0.6.tgz", + "integrity": "sha512-xMe2gLHHjkN2s0mMgaGjvSAhLe1elyk3HXcTru1sMUDKIEvJLer59yrTI57GQbsmJT/3rLe6AQhzSmzxGOcSkg==", "dependencies": { - "@loopback/model-api-builder": "^6.0.4", - "@loopback/repository": "^7.0.4", - "@loopback/service-proxy": "^7.0.4", + "@loopback/model-api-builder": "^6.0.6", + "@loopback/repository": "^7.0.6", + "@loopback/service-proxy": "^7.0.6", "@types/debug": "^4.1.12", "@types/glob": "^8.1.0", - "debug": "^4.3.5", - "glob": "^10.4.2", + "debug": "^4.3.7", + "glob": "^10.4.5", "tslib": "^2.6.3" }, "engines": { @@ -1547,23 +1547,23 @@ } }, "node_modules/@loopback/build": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@loopback/build/-/build-11.0.4.tgz", - "integrity": "sha512-Ilm7GabuRqinsyZDh/rLJc0PeY0X7gbFZ3LZnuwfok0ywrFA38DnP6/PU24A+2XkypufJOm0MaWVgiHJRGWu2w==", + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/@loopback/build/-/build-11.0.6.tgz", + "integrity": "sha512-9m2k0hkL9q6uwqFXNComp+r8Qar2GZIenlQET36nkFxkzXi/Nex+u4+0/FIo8QN0sLC56Eek9+5vt2/XVBBtJw==", "dev": true, "dependencies": { - "@loopback/eslint-config": "^15.0.3", + "@loopback/eslint-config": "^15.0.4", "@types/mocha": "^10.0.7", "@types/node": "^16.18.101", "cross-spawn": "^7.0.3", - "debug": "^4.3.5", + "debug": "^4.3.7", "eslint": "^8.57.0", "fs-extra": "^11.2.0", - "glob": "^10.4.2", + "glob": "^10.4.5", "lodash": "^4.17.21", "mocha": "^10.6.0", "nyc": "^17.0.0", - "prettier": "^3.2.5", + "prettier": "^3.3.2", "rimraf": "^5.0.7", "source-map-support": "^0.5.21", "typescript": "~5.2.2" @@ -1582,9 +1582,9 @@ } }, "node_modules/@loopback/build/node_modules/@types/node": { - "version": "16.18.104", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.104.tgz", - "integrity": "sha512-OF3keVCbfPlkzxnnDBUZJn1RiCJzKeadjiW0xTEb0G1SUJ5gDVb3qnzZr2T4uIFvsbKJbXy1v2DN7e2zaEY7jQ==", + "version": "16.18.111", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.111.tgz", + "integrity": "sha512-U1l6itlxU+vrJ9KyowQLKV9X+UuQBRhBF9v/XkGhAGgNHHRWzyY7FfTYRXt3vYOXPrd8UGlbYFK5HdneKCwXPQ==", "dev": true }, "node_modules/@loopback/build/node_modules/brace-expansion": { @@ -1621,19 +1621,6 @@ "node": ">=8" } }, - "node_modules/@loopback/build/node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@loopback/build/node_modules/fs-extra": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", @@ -1713,9 +1700,9 @@ } }, "node_modules/@loopback/build/node_modules/nyc": { - "version": "17.0.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.0.0.tgz", - "integrity": "sha512-ISp44nqNCaPugLLGGfknzQwSwt10SSS5IMoPR7GLoMAyS18Iw5js8U7ga2VF9lYuMZ42gOHr3UddZw4WZltxKg==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.1.0.tgz", + "integrity": "sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ==", "dev": true, "dependencies": { "@istanbuljs/load-nyc-config": "^1.0.0", @@ -1725,7 +1712,7 @@ "decamelize": "^1.2.0", "find-cache-dir": "^3.2.0", "find-up": "^4.1.0", - "foreground-child": "^2.0.0", + "foreground-child": "^3.3.0", "get-package-type": "^0.1.0", "glob": "^7.1.6", "istanbul-lib-coverage": "^3.0.0", @@ -1910,13 +1897,13 @@ } }, "node_modules/@loopback/context": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/context/-/context-7.0.4.tgz", - "integrity": "sha512-NWtXJ2mH3Akj+Qlj4hPd4RZXyIkF4vS0DOZZXsHorPFAmvMdNi32SOXjUm/Je59k5xHBMQEW53tey8P+9iJXHQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@loopback/context/-/context-7.0.6.tgz", + "integrity": "sha512-a9NvaNfSxbgm8bs75ZyqHyqlptULqehcfAXpZq8TDMHyyJ4jVk7KpAab2YwVztzJUbgeagVv1IAHL1Gz0Qwr3g==", "dependencies": { - "@loopback/metadata": "^7.0.4", + "@loopback/metadata": "^7.0.6", "@types/debug": "^4.1.12", - "debug": "^4.3.5", + "debug": "^4.3.7", "hyperid": "^3.2.0", "p-event": "^4.2.0", "tslib": "^2.6.3", @@ -1927,12 +1914,12 @@ } }, "node_modules/@loopback/core": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@loopback/core/-/core-6.1.1.tgz", - "integrity": "sha512-gphIDW8sT1+0f6QqPc1h+P3l92oaMfMd1VK8DHtyCZwrbBGJkbuZC2BgzGOQyPXhgoLbIVO07shjmjgygaV2sg==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@loopback/core/-/core-6.1.3.tgz", + "integrity": "sha512-rFs16z00cavqQeXdvFRx9CnyinPTI4qWvtnDJ/PsZSCWuvG9qBbK69aub9J+PPX/SHIgHsMExB9uNjyMycnieg==", "dependencies": { - "@loopback/context": "^7.0.4", - "debug": "^4.3.5", + "@loopback/context": "^7.0.6", + "debug": "^4.3.7", "tslib": "^2.6.3" }, "engines": { @@ -1940,13 +1927,13 @@ } }, "node_modules/@loopback/eslint-config": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@loopback/eslint-config/-/eslint-config-15.0.3.tgz", - "integrity": "sha512-Rv/o0qtEXMTIhLU2IFY9Mgp2eKDv732bG3UuVPXYROrM5P3AJkCO6tuonCDDrm6w4KTAL2xoJ9V+Fzcb8opxog==", + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@loopback/eslint-config/-/eslint-config-15.0.4.tgz", + "integrity": "sha512-ZmCd/2qoSQrKzvM4pBmSJJ1KAecVvFFimGQjZgafmxe7J5Jfb1bamLoDYMcMI1DmKCRxd77nweX1PJHC4AmGtg==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.10.0", - "@typescript-eslint/parser": "^7.10.0", + "@typescript-eslint/eslint-plugin": "^7.16.0", + "@typescript-eslint/parser": "^7.16.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-eslint-plugin": "^5.5.1", "eslint-plugin-mocha": "^10.4.3" @@ -1959,17 +1946,17 @@ } }, "node_modules/@loopback/express": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/express/-/express-7.0.4.tgz", - "integrity": "sha512-JraWV1WD5FHX3/8HEMfu7BC7GcS5kK/o4NwV0fuNiiv9sD+4mnWy7mgo+w3TbyfDSxq5BKspPWSoGMsUXYnwAw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@loopback/express/-/express-7.0.6.tgz", + "integrity": "sha512-zey+J7ateOHrXIv9yq7F2RlS3Hk4lkc+fAqtORCdkoEJWblQ/oe1/gm4hYQWKnO3KDQ0pktfa3EEZlGzU0mG9Q==", "dependencies": { - "@loopback/http-server": "^6.0.4", + "@loopback/http-server": "^6.0.6", "@types/body-parser": "^1.19.5", "@types/express": "^4.17.21", "@types/express-serve-static-core": "^4.17.37", "@types/http-errors": "^2.0.4", - "body-parser": "^1.20.2", - "debug": "^4.3.5", + "body-parser": "^1.20.3", + "debug": "^4.3.7", "express": "^4.19.2", "http-errors": "^2.0.0", "on-finished": "^2.4.1", @@ -1984,9 +1971,9 @@ } }, "node_modules/@loopback/filter": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@loopback/filter/-/filter-5.0.4.tgz", - "integrity": "sha512-16SdyFe+mh8b6JIgbnS1NIiop+6vhh62BQ0hOBfRVnffCf1NEE3mrERNsbMNou9eMx/OOIBMfhoAoFwfSfyJdw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@loopback/filter/-/filter-5.0.6.tgz", + "integrity": "sha512-rGQaVrUtJGtUvZ/4bRS+4dbAqPOziGY1GyM6dsLalM0xR9paZVTZSvNSYK1cK0eagzdgUjFbVHwhaJ0pdjIYCQ==", "dependencies": { "tslib": "^2.6.3" }, @@ -1995,11 +1982,11 @@ } }, "node_modules/@loopback/http-server": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@loopback/http-server/-/http-server-6.0.4.tgz", - "integrity": "sha512-e5QyUeX4ime9mbnPOO8la7AccRCJ9Iyw/+HSRDoFV0ufOCZFwVkzu8NLM/NJPyFTPcMe+jTddvgH0+KcmBYCwA==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@loopback/http-server/-/http-server-6.0.6.tgz", + "integrity": "sha512-3GhrLWHi87VVL2jGSpFocfsKYGjBIjoJ8r017/kuMi4UGweNrbSBrL8ly5tKQ6APGjx+/j9qEuFC1IaaXo4FCw==", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.3.7", "stoppable": "^1.1.0", "tslib": "^2.6.3" }, @@ -2008,9 +1995,9 @@ } }, "node_modules/@loopback/logging": { - "version": "0.12.4", - "resolved": "https://registry.npmjs.org/@loopback/logging/-/logging-0.12.4.tgz", - "integrity": "sha512-+30PxdR2F8V9mrUxTD7eHjhV46LgAyVbZfv4+C9Sjrpa81+e2V/uXBai8o6Ir1pJsQo6fm97EohLttYV3LrNUA==", + "version": "0.12.6", + "resolved": "https://registry.npmjs.org/@loopback/logging/-/logging-0.12.6.tgz", + "integrity": "sha512-v6HpSCVCOzpq7gS9bx6bO42oMa4kKD9NhXfZ1nUJAr6M2ErSzwxdQI1m67CW5DYr8OoziQfO9GSFcqpbXsPI7w==", "dev": true, "dependencies": { "fluent-logger": "^3.4.1", @@ -2028,11 +2015,11 @@ } }, "node_modules/@loopback/metadata": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/metadata/-/metadata-7.0.4.tgz", - "integrity": "sha512-PMR9FYnWbx9FcnF/oy5ukY852l2xl7eUU1WMOVeiYU66vpXdCXb1bOoTZjMOAuqZcSlQUjqwk15cZzydU/ITxw==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@loopback/metadata/-/metadata-7.0.6.tgz", + "integrity": "sha512-NlSx6VcGC97qFiC4yR+3UcT7n7FpEGpyqPwYmbLPiIyKsbv/s5TwCUy/1QprJAx9ZFrM/mkd0nW2z01Ek6PnUw==", "dependencies": { - "debug": "^4.3.5", + "debug": "^4.3.7", "lodash": "^4.17.21", "reflect-metadata": "^0.2.1", "tslib": "^2.6.3" @@ -2042,9 +2029,9 @@ } }, "node_modules/@loopback/model-api-builder": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@loopback/model-api-builder/-/model-api-builder-6.0.4.tgz", - "integrity": "sha512-Or3vS/q76FPZ3voLkvLTP0j3kewa+gM4A3sf0wzGVFEe2/mq2Kkvx2e5hMe/siKEe9VfythQ2SSvf4uJz0zFhg==", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/@loopback/model-api-builder/-/model-api-builder-6.0.6.tgz", + "integrity": "sha512-KqluBGENq/VRhgFriyLFPzMAtCEmUbgZ3PUpiFWVzNRLmGccQwl+zfPKNwhl8Q8A5yXNS3y7ANzxcKo/1i2chA==", "dependencies": { "tslib": "^2.6.3" }, @@ -2057,12 +2044,12 @@ } }, "node_modules/@loopback/openapi-v3": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@loopback/openapi-v3/-/openapi-v3-10.0.4.tgz", - "integrity": "sha512-ZZSKd7pMNDP7BgvHcWUeAV3DqTA9g89C6Ra4wCmCHnpvQdxoqoNWI10wupRFKFgbLr/x9YK/Pf9CnwN+WZQIMQ==", + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@loopback/openapi-v3/-/openapi-v3-10.0.6.tgz", + "integrity": "sha512-pOmYzuv58t5eA0gZKuAeT6GcTBV0+n25OSUjx5CpwhXnq9hfFku7USWUfJLgwEuE75XsTQ7dUUmmBYvpaFCw2Q==", "dependencies": { - "@loopback/repository-json-schema": "^8.0.4", - "debug": "^4.3.5", + "@loopback/repository-json-schema": "^8.0.6", + "debug": "^4.3.7", "http-status": "^1.7.4", "json-merge-patch": "^1.0.2", "lodash": "^4.17.21", @@ -2077,13 +2064,13 @@ } }, "node_modules/@loopback/repository": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/repository/-/repository-7.0.4.tgz", - "integrity": "sha512-SP9i/38N/S3p8eBhrWSv2or+hRXFkZ6o5bFFr2QvuZtMEFP6OUR1bTUfSlElWGddOAUOpwh7COnn5evezwRhdQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@loopback/repository/-/repository-7.0.6.tgz", + "integrity": "sha512-yxImM2VqC4sD7e++y4KTP9Clp6SymRS2EgTYCUMZ+k+dQ3LqIjQwg6Ro5g0VuQpJMNBr3d0qDW16rPRV9o5VFw==", "dependencies": { - "@loopback/filter": "^5.0.4", + "@loopback/filter": "^5.0.6", "@types/debug": "^4.1.12", - "debug": "^4.3.5", + "debug": "^4.3.7", "lodash": "^4.17.21", "loopback-datasource-juggler": "^5.0.11", "tslib": "^2.6.3" @@ -2096,12 +2083,12 @@ } }, "node_modules/@loopback/repository-json-schema": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@loopback/repository-json-schema/-/repository-json-schema-8.0.4.tgz", - "integrity": "sha512-WUWEQIw66rsw6EHVcsel7Bty/MV93E0HUvn8dcSHexKhoGv/9oae1EYUsaThkjb4hnG2iYZt4ZNDL4SyMJ6aXg==", + "version": "8.0.6", + "resolved": "https://registry.npmjs.org/@loopback/repository-json-schema/-/repository-json-schema-8.0.6.tgz", + "integrity": "sha512-oYVURgSIHLhzwuL3J4CwokDQ4Pki1OKnGZerR/Z3sZhcBdP/CwJk1AwZ3XuxTBHS2ebfW7H1Kg9EoOoHRrojtA==", "dependencies": { "@types/json-schema": "^7.0.15", - "debug": "^4.3.5", + "debug": "^4.3.7", "tslib": "^2.6.3" }, "engines": { @@ -2113,13 +2100,13 @@ } }, "node_modules/@loopback/rest": { - "version": "14.0.4", - "resolved": "https://registry.npmjs.org/@loopback/rest/-/rest-14.0.4.tgz", - "integrity": "sha512-1oQBJ1J+i7i4NsA9WzhGZJ2R9CK/aK2VTbQb6wlIgeSg5t65uSqUYUYgcY/Gt1wOJudZQ6l0CQJKhK1sdZj7+A==", + "version": "14.0.6", + "resolved": "https://registry.npmjs.org/@loopback/rest/-/rest-14.0.6.tgz", + "integrity": "sha512-/YPSwhzCNnvkkc3kMhATkg2CeMy4rklGd9dTvXHNLJgZ/KplvwKRlI+IKAKYeUcnc5m51eDD3YcUBjlAlg9wfQ==", "dependencies": { - "@loopback/express": "^7.0.4", - "@loopback/http-server": "^6.0.4", - "@loopback/openapi-v3": "^10.0.4", + "@loopback/express": "^7.0.6", + "@loopback/http-server": "^6.0.6", + "@loopback/openapi-v3": "^10.0.6", "@openapi-contrib/openapi-schema-to-json-schema": "^5.1.0", "@types/body-parser": "^1.19.5", "@types/cors": "^2.8.17", @@ -2133,9 +2120,9 @@ "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", "ajv-keywords": "^5.1.0", - "body-parser": "^1.20.2", + "body-parser": "^1.20.3", "cors": "^2.8.5", - "debug": "^4.3.5", + "debug": "^4.3.7", "express": "^4.19.2", "http-errors": "^2.0.0", "js-yaml": "^4.1.0", @@ -2143,7 +2130,7 @@ "lodash": "^4.17.21", "on-finished": "^2.4.1", "path-to-regexp": "^6.2.2", - "qs": "^6.12.2", + "qs": "^6.12.3", "strong-error-handler": "^5.0.10", "tslib": "^2.6.3", "type-is": "^1.6.18", @@ -2157,9 +2144,9 @@ } }, "node_modules/@loopback/rest-explorer": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/rest-explorer/-/rest-explorer-7.0.4.tgz", - "integrity": "sha512-qz9gdjVIQ7YzH48iDBaL/lW4B40faR+Peg6AnJE0yetLG6DBi2Sm4yjzSFU7IGz4PbPG5wt1lDxdrutWK632lA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@loopback/rest-explorer/-/rest-explorer-7.0.6.tgz", + "integrity": "sha512-YU8bRw+oNIXubFoIgZWVwGXWweSl+N+vyvVmcv6c+pcT8aaLc+HWT1OAiRiVIl1U6EzdAvC6igBpKUsFbjHwtA==", "dependencies": { "ejs": "^3.1.10", "swagger-ui-dist": "5.17.14", @@ -2213,9 +2200,9 @@ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" }, "node_modules/@loopback/service-proxy": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/service-proxy/-/service-proxy-7.0.4.tgz", - "integrity": "sha512-Nw1S71eqNszN874AwmoRLQcT7aocD7E6aomB0Nff5jTTDONkZpIE+Zancyn0EtVp58DTVa2K+x9sPfiaL1XwYQ==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@loopback/service-proxy/-/service-proxy-7.0.6.tgz", + "integrity": "sha512-MS57pxCMQl9G5w22Hf2bGGjTaaA+JwjdloOjrqo5oroJXtjgMaGbZZ+h7FM59WQb3LP9qTlUCg4xtXrU04uMlw==", "dependencies": { "loopback-datasource-juggler": "^5.0.11", "tslib": "^2.6.3" @@ -2228,9 +2215,9 @@ } }, "node_modules/@loopback/testlab": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/@loopback/testlab/-/testlab-7.0.4.tgz", - "integrity": "sha512-8ZuiyMpdSuIHyA3Hxe+XyH9vEEndKTr093uPsSwevmks+oInCwMB9Y7Op5t2Ef9SEU+p8iSpfW8ikf2cp8VJ0g==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@loopback/testlab/-/testlab-7.0.6.tgz", + "integrity": "sha512-pRYqTKB7HpDRJrM1pHapMSnBDWWzD9cs2BFSXcYcq061xFGYjqyZuMi7BWT1vz4iVGQ0zKt+qyqmCVWONmqalw==", "dev": true, "dependencies": { "@hapi/shot": "^6.0.1", @@ -3006,18 +2993,13 @@ } }, "node_modules/@openapi-contrib/openapi-schema-to-json-schema/node_modules/@types/node": { - "version": "20.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.14.tgz", - "integrity": "sha512-d64f00982fS9YoOgJkAMolK7MN8Iq3TDdVjchbYHdEmjth/DHowx82GnoA+tVUAN+7vxfYUgAzi+JXbKNd2SDQ==", + "version": "20.16.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.9.tgz", + "integrity": "sha512-rkvIVJxsOfBejxK7I0FO5sa2WxFmJCzoDwcd88+fq/CUfynNywTo/1/T6hyFz22CyztsnLS9nVlHOnTI36RH5w==", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, - "node_modules/@openapi-contrib/openapi-schema-to-json-schema/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -3028,9 +3010,9 @@ } }, "node_modules/@opentelemetry/context-async-hooks": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.25.1.tgz", - "integrity": "sha512-UW/ge9zjvAEmRWVapOP0qyCvPulWU6cQxGxDbWEFfGOj1VBBZAuOqTo3X6yWmDTD3Xe15ysCZChHncr2xFMIfQ==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz", + "integrity": "sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg==", "engines": { "node": ">=14" }, @@ -3039,11 +3021,11 @@ } }, "node_modules/@opentelemetry/core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.25.1.tgz", - "integrity": "sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-1.26.0.tgz", + "integrity": "sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==", "dependencies": { - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/semantic-conventions": "1.27.0" }, "engines": { "node": ">=14" @@ -3053,13 +3035,13 @@ } }, "node_modules/@opentelemetry/exporter-jaeger": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-jaeger/-/exporter-jaeger-1.25.1.tgz", - "integrity": "sha512-6/HwzrwUx0fpkFXrouF0IJp+hpN8xkx8RqEk+BZfeoMAHydpyigyYsKyAtAZRwfJe45WWJbJUqoK8aBjiC9iLQ==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/exporter-jaeger/-/exporter-jaeger-1.26.0.tgz", + "integrity": "sha512-l5NMFwFr5NWWRNcURUS8/RdkBmR3+dPGE33f51XfamKXsEfZUkRC8V1L2D7hzKhXxcFmYLcprg4/sYpeKtYoAQ==", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1", + "@opentelemetry/core": "1.26.0", + "@opentelemetry/sdk-trace-base": "1.26.0", + "@opentelemetry/semantic-conventions": "1.27.0", "jaeger-client": "^3.15.0" }, "engines": { @@ -3262,11 +3244,11 @@ } }, "node_modules/@opentelemetry/propagator-b3": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.25.1.tgz", - "integrity": "sha512-p6HFscpjrv7//kE+7L+3Vn00VEDUJB0n6ZrjkTYHrJ58QZ8B3ajSJhRbCcY6guQ3PDjTbxWklyvIN2ojVbIb1A==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-b3/-/propagator-b3-1.26.0.tgz", + "integrity": "sha512-vvVkQLQ/lGGyEy9GT8uFnI047pajSOVnZI2poJqVGD3nJ+B9sFGdlHNnQKophE3lHfnIH0pw2ubrCTjZCgIj+Q==", "dependencies": { - "@opentelemetry/core": "1.25.1" + "@opentelemetry/core": "1.26.0" }, "engines": { "node": ">=14" @@ -3276,11 +3258,11 @@ } }, "node_modules/@opentelemetry/propagator-jaeger": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.25.1.tgz", - "integrity": "sha512-nBprRf0+jlgxks78G/xq72PipVK+4or9Ypntw0gVZYNTCSK8rg5SeaGV19tV920CMqBD/9UIOiFr23Li/Q8tiA==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/propagator-jaeger/-/propagator-jaeger-1.26.0.tgz", + "integrity": "sha512-DelFGkCdaxA1C/QA0Xilszfr0t4YbGd3DjxiCDPh34lfnFr+VkkrjV9S8ZTJvAzfdKERXhfOxIKBoGPJwoSz7Q==", "dependencies": { - "@opentelemetry/core": "1.25.1" + "@opentelemetry/core": "1.26.0" }, "engines": { "node": ">=14" @@ -3290,12 +3272,12 @@ } }, "node_modules/@opentelemetry/resources": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.25.1.tgz", - "integrity": "sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-1.26.0.tgz", + "integrity": "sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw==", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/core": "1.26.0", + "@opentelemetry/semantic-conventions": "1.27.0" }, "engines": { "node": ">=14" @@ -3305,13 +3287,13 @@ } }, "node_modules/@opentelemetry/sdk-trace-base": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.25.1.tgz", - "integrity": "sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz", + "integrity": "sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw==", "dependencies": { - "@opentelemetry/core": "1.25.1", - "@opentelemetry/resources": "1.25.1", - "@opentelemetry/semantic-conventions": "1.25.1" + "@opentelemetry/core": "1.26.0", + "@opentelemetry/resources": "1.26.0", + "@opentelemetry/semantic-conventions": "1.27.0" }, "engines": { "node": ">=14" @@ -3321,15 +3303,15 @@ } }, "node_modules/@opentelemetry/sdk-trace-node": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.25.1.tgz", - "integrity": "sha512-nMcjFIKxnFqoez4gUmihdBrbpsEnAX/Xj16sGvZm+guceYE0NE00vLhpDVK6f3q8Q4VFI5xG8JjlXKMB/SkTTQ==", - "dependencies": { - "@opentelemetry/context-async-hooks": "1.25.1", - "@opentelemetry/core": "1.25.1", - "@opentelemetry/propagator-b3": "1.25.1", - "@opentelemetry/propagator-jaeger": "1.25.1", - "@opentelemetry/sdk-trace-base": "1.25.1", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-1.26.0.tgz", + "integrity": "sha512-Fj5IVKrj0yeUwlewCRwzOVcr5avTuNnMHWf7GPc1t6WaT78J6CJyF3saZ/0RkZfdeNO8IcBl/bNcWMVZBMRW8Q==", + "dependencies": { + "@opentelemetry/context-async-hooks": "1.26.0", + "@opentelemetry/core": "1.26.0", + "@opentelemetry/propagator-b3": "1.26.0", + "@opentelemetry/propagator-jaeger": "1.26.0", + "@opentelemetry/sdk-trace-base": "1.26.0", "semver": "^7.5.2" }, "engines": { @@ -3351,9 +3333,9 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz", - "integrity": "sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==", + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz", + "integrity": "sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==", "engines": { "node": ">=14" } @@ -3652,35 +3634,35 @@ } }, "node_modules/@sinonjs/samsam": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", - "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.2.tgz", + "integrity": "sha512-v46t/fwnhejRSFTGqbpn9u+LQ9xJDse10gNnPgAcxgdoCDMXj/G2asWAC/8Qs+BAZDicX+MNZouXT1A7c83kVw==", "dev": true, "dependencies": { - "@sinonjs/commons": "^2.0.0", + "@sinonjs/commons": "^3.0.1", "lodash.get": "^4.4.2", - "type-detect": "^4.0.8" + "type-detect": "^4.1.0" } }, - "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "node_modules/@sinonjs/samsam/node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, - "dependencies": { - "type-detect": "4.0.8" + "engines": { + "node": ">=4" } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "node_modules/@sourceloop/core": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@sourceloop/core/-/core-14.1.1.tgz", - "integrity": "sha512-nc13D5UMbF1dU9qsLMkDupaQLsCOrNnT0angXTApIc5DmoB+7vwIHFBpm8Q7RHXh18+e7TSMmgPpFNSttpV6+w==", + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@sourceloop/core/-/core-14.1.2.tgz", + "integrity": "sha512-eZvDLXhRjHzx/FC7J2z3nkQU7Cj84meWZ62uyJYTwclQ+1l7S41x1mA03AM7K//F02yKfiU1x4BoYeY1JWeRaQ==", "dependencies": { "@loopback/boot": "^7.0.2", "@loopback/context": "^7.0.2", @@ -3735,9 +3717,9 @@ "link": true }, "node_modules/@sourceloop/feature-toggle-service": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sourceloop/feature-toggle-service/-/feature-toggle-service-3.0.0.tgz", - "integrity": "sha512-zmTO9T0jqwqXayXMhfXnhb71k6mJQysDbboMHuCQq+I31Jq33rzsq+I0op0PG+bw3yRh97yD6EaySrGWcsSn4A==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sourceloop/feature-toggle-service/-/feature-toggle-service-3.0.1.tgz", + "integrity": "sha512-mq60cAE2csqJfOBmUKasoGLn6nC1Pwup/afBy+y34ScKxkeChVfvubz/0oE/0vCc90xFbZ7Ui399JRHSj621uw==", "hasInstallScript": true, "dependencies": { "@loopback/boot": "^7.0.2", @@ -3748,7 +3730,7 @@ "@loopback/rest": "^14.0.2", "@loopback/rest-explorer": "^7.0.2", "@loopback/service-proxy": "^7.0.2", - "@sourceloop/core": "^14.1.1", + "@sourceloop/core": "^14.1.2", "@types/proxyquire": "^1.3.28", "dotenv": "^16.0.3", "dotenv-extended": "^2.9.0", @@ -3830,10 +3812,16 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@types/auth0": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/@types/auth0/-/auth0-3.3.10.tgz", + "integrity": "sha512-9tS0Y2igWxw+Dx5uCHkIUCu6tG0oRkwpE322dOJPwZMLXQMx49n/gDmUz7YJSe1iVjrWW+ffVYmlPShVIEwjkg==", + "dev": true + }, "node_modules/@types/aws-lambda": { - "version": "8.10.142", - "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.142.tgz", - "integrity": "sha512-wy2y/2hQKrS6myOS++koXg3N1Hg+LLyPjaggCFajczSHZPqBnOMuT2sdH3kiASrmdBYyM3pmjyz5SoWraRllCQ==", + "version": "8.10.145", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.145.tgz", + "integrity": "sha512-dtByW6WiFk5W5Jfgz1VM+YPA21xMXTuSFoLYIDY0L44jDLLflVPtZkYuu3/YxpGcvjzKFBZLU+GyKjR0HOYtyw==", "dev": true }, "node_modules/@types/body-parser": { @@ -3887,9 +3875,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.19.5", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", - "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -3942,17 +3930,17 @@ } }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz", - "integrity": "sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/lodash": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", - "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==" + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.9.tgz", + "integrity": "sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w==" }, "node_modules/@types/methods": { "version": "1.1.4", @@ -3977,9 +3965,9 @@ "dev": true }, "node_modules/@types/mocha": { - "version": "10.0.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.7.tgz", - "integrity": "sha512-GN8yJ1mNTcFcah/wKEFIJckJx9iJLoMSzWcfRRuxz/Jk+U6KQNnml+etbtxFK8lPjzOw3zp4Ha/kjSst9fsHYw==", + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.8.tgz", + "integrity": "sha512-HfMcUmy9hTMJh66VNcmeC9iVErIZJli2bszuXc6julh5YGuRb/W5OnkHjwLNYdFlMis0sY3If5SEAp+PktdJjw==", "dev": true }, "node_modules/@types/moment": { @@ -3998,11 +3986,11 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "22.1.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", - "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", + "version": "22.7.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.3.tgz", + "integrity": "sha512-qXKfhXXqGTyBskvWEzJZPUxSslAiLaB6JGP1ic/XTH9ctGgzdgYguuLP1C601aRTSDNlLb0jbKqXjZ48GNraSA==", "dependencies": { - "undici-types": "~6.13.0" + "undici-types": "~6.19.2" } }, "node_modules/@types/normalize-package-data": { @@ -4026,18 +4014,18 @@ "dev": true }, "node_modules/@types/pdfkit": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.13.4.tgz", - "integrity": "sha512-ixGNDHYJCCKuamY305wbfYSphZ2WPe8FPkjn8oF4fHV+PgPV4V+hecPh2VOS2h4RNtpSB3zQcR4sCpNvvrEb1A==", + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.13.5.tgz", + "integrity": "sha512-cR4gZA3xiMVDUf/O/ijVr6aIguvN72ZmCDLcWwM0ycqn5P8XDSjMX9PixzWEoDnlINoofxo2LCdV4KvdD9Waqg==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/pg": { - "version": "8.11.6", - "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.6.tgz", - "integrity": "sha512-/2WmmBXHLsfRqzfHW7BNZ8SbYzE8OSk7i3WjFYvfgRHj7S1xj+16Je5fUKv3lVdVzk/zn9TXOqf+avFCFIE0yQ==", + "version": "8.11.10", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.10.tgz", + "integrity": "sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==", "dependencies": { "@types/node": "*", "pg-protocol": "*", @@ -4050,9 +4038,9 @@ "integrity": "sha512-uALowNG2TSM1HNPMMOR0AJwv4aPYPhqB0xlEhkeRTMuto5hjoSPZkvgu1nbPUkz3gEPAHv4sy4DmKsurZiEfRQ==" }, "node_modules/@types/qs": { - "version": "6.9.15", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", - "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==" + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==" }, "node_modules/@types/range-parser": { "version": "1.2.7", @@ -4103,9 +4091,9 @@ "dev": true }, "node_modules/@types/superagent": { - "version": "8.1.8", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.8.tgz", - "integrity": "sha512-nTqHJ2OTa7PFEpLahzSEEeFeqbMpmcN7OeayiOc7v+xk+/vyTKljRe+o4MPqSnPeRCMvtxuLG+5QqluUVQJOnA==", + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", "dev": true, "dependencies": { "@types/cookiejar": "^2.1.5", @@ -4417,12 +4405,11 @@ "dev": true }, "node_modules/accept-language": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.18.tgz", - "integrity": "sha512-sUofgqBPzgfcF20sPoBYGQ1IhQLt2LSkxTnlQSuLF3n5gPEqd5AimbvOvHEi0T1kLMiGVqPWzI5a9OteBRth3A==", + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/accept-language/-/accept-language-3.0.20.tgz", + "integrity": "sha512-xklPzRma4aoDEPk0ZfMjeuxB2FP4JBYlAR25OFUqCoOYDjYo6wGwAs49SnTN/MoB5VpnNX9tENfZ+vEIFmHQMQ==", "dependencies": { - "bcp47": "^1.1.2", - "stable": "^0.1.6" + "bcp47": "^1.1.2" } }, "node_modules/accepts": { @@ -4459,9 +4446,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", - "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "dev": true, "dependencies": { "acorn": "^8.11.0" @@ -4789,9 +4776,9 @@ } }, "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" }, "node_modules/asynckit": { "version": "0.4.0", @@ -4807,6 +4794,33 @@ "node": ">= 4.0.0" } }, + "node_modules/auth0": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/auth0/-/auth0-4.10.0.tgz", + "integrity": "sha512-xfNtSyL84w9z1DQXWV1GXgtq2Oi3OXeJe/r+pI29GKZHpfgspNb4rFqp/CqI8zKVir6L3Iq2KZgE2rDHRDtxfA==", + "dev": true, + "dependencies": { + "jose": "^4.13.2", + "undici-types": "^6.15.0", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/auth0/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/auto-parse": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/auto-parse/-/auto-parse-1.8.0.tgz", @@ -4844,15 +4858,15 @@ } }, "node_modules/aws4": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", - "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", "peer": true }, "node_modules/axios": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.3.tgz", - "integrity": "sha512-Ar7ND9pU99eJ9GpoGQKhKf58GpUOgnzuaB7ueNQ5BMi0p+LZ5oaEnfF999fAArcTIBwXTCHAmGcHOZJaWPq9Nw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -4957,11 +4971,6 @@ "safe-buffer": "^5.1.1" } }, - "node_modules/bl/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "node_modules/bl/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -4995,9 +5004,9 @@ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -5007,7 +5016,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -5030,20 +5039,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -5084,9 +5079,9 @@ "dev": true }, "node_modules/browserslist": { - "version": "4.23.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", - "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "version": "4.24.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.0.tgz", + "integrity": "sha512-Rmb62sR1Zpjql25eSanFGEhAxcFwfA1K0GuQcLoaJBAcENegrQut3hYdhXFF1obQfiDyqIW/cLM5HSJ/9k884A==", "dev": true, "funding": [ { @@ -5103,8 +5098,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001646", - "electron-to-chromium": "^1.5.4", + "caniuse-lite": "^1.0.30001663", + "electron-to-chromium": "^1.5.28", "node-releases": "^2.0.18", "update-browserslist-db": "^1.1.0" }, @@ -5478,9 +5473,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001649", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001649.tgz", - "integrity": "sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ==", + "version": "1.0.30001664", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001664.tgz", + "integrity": "sha512-AmE7k4dXiNKQipgn7a2xg558IRqPN3jMQY/rOsbxDhrd0tyChwbITBfiwtnqz8bi2M5mIWbxAYBvk7W7QBUS2g==", "dev": true, "funding": [ { @@ -5620,6 +5615,18 @@ "node": "*" } }, + "node_modules/chargebee": { + "version": "2.42.0", + "resolved": "https://registry.npmjs.org/chargebee/-/chargebee-2.42.0.tgz", + "integrity": "sha512-kqWkGnPmUFDn+i2QkI9W3//SV2uihIGu9t0xE9QGXpsvnTj9V9BX5SBbUa4cWv/Kl4wMRNB1I94bYYwD6pe6KA==", + "dependencies": { + "q": ">=1.0.1", + "safer-buffer": "2.1.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -5881,9 +5888,9 @@ } }, "node_modules/commitizen": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.3.0.tgz", - "integrity": "sha512-H0iNtClNEhT0fotHvGV3E9tDejDeS04sN1veIebsKYGMuGscFaswRoYJKmT3eW85eIJAs0F28bG2+a/9wCOfPw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/commitizen/-/commitizen-4.3.1.tgz", + "integrity": "sha512-gwAPAVTy/j5YcOOebcCRIijn+mSjWJC+IYKivTu6aG8Ei/scoXgfsMRnuAk6b0GRste2J4NGxVdMN3ZpfNaVaw==", "dev": true, "dependencies": { "cachedir": "2.3.0", @@ -6985,11 +6992,11 @@ } }, "node_modules/debug": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", - "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -7071,6 +7078,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-equal/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -7409,9 +7421,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.4.tgz", - "integrity": "sha512-orzA81VqLyIGUEA77YkVA1D+N+nNfl2isJVjjmOyrlxuooZ19ynb+dOlaDTqd/idKRS9lDCSBmtzM+kyCsMnkA==", + "version": "1.5.29", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.29.tgz", + "integrity": "sha512-PF8n2AlIhCKXQ+gTpiJi0VhcHDb69kYX4MtCiivctc2QD3XuNZ/XIOlbGzt7WAjjEev0TtaH6Cu3arZExm5DOw==", "dev": true }, "node_modules/emoji-regex": { @@ -7425,9 +7437,9 @@ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -7617,6 +7629,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-get-iterator/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/es-object-atoms": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", @@ -7664,9 +7681,9 @@ "dev": true }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { "node": ">=6" } @@ -7689,16 +7706,16 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -7999,36 +8016,36 @@ "dev": true }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -8064,23 +8081,9 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/express/node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/expression-eval": { "version": "5.0.1", @@ -8181,9 +8184,9 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" }, "node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.2.tgz", + "integrity": "sha512-GR6f0hD7XXyNJa25Tb9BuIdN0tdr+0BMi6/CJPH3wJO1JjNG3n/VsSw38AwRdKZABm8lGbPfakLRkYzx2V9row==" }, "node_modules/fastq": { "version": "1.17.1", @@ -8289,12 +8292,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -8524,9 +8527,9 @@ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", "funding": [ { "type": "individual", @@ -8567,9 +8570,9 @@ } }, "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" @@ -8867,12 +8870,6 @@ "wrap-ansi": "^7.0.0" } }, - "node_modules/get-pkg-repo/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, "node_modules/get-pkg-repo/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -9598,9 +9595,9 @@ } }, "node_modules/hyperid": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-3.2.0.tgz", - "integrity": "sha512-PdTtDo+Rmza9nEhTunaDSUKwbC69TIzLEpZUwiB6f+0oqmY0UPfhyHCPt6K1NQ4WFv5yJBTG5vELztVWP+nEVQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/hyperid/-/hyperid-3.3.0.tgz", + "integrity": "sha512-7qhCVT4MJIoEsNcbhglhdmBKb09QtcmJNiIQGq7js/Khf5FtQQ9bzcAuloeqBeee7XD7JqDeve9KNlQya5tSGQ==", "dependencies": { "buffer": "^5.2.1", "uuid": "^8.3.2", @@ -9665,9 +9662,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" @@ -10079,9 +10076,9 @@ } }, "node_modules/is-core-module": { - "version": "2.15.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.0.tgz", - "integrity": "sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dependencies": { "hasown": "^2.0.2" }, @@ -10463,9 +10460,9 @@ } }, "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/isexe": { "version": "2.0.0", @@ -10784,6 +10781,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/jpeg-exif": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz", @@ -11827,9 +11833,9 @@ } }, "node_modules/loopback-connector-postgresql": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/loopback-connector-postgresql/-/loopback-connector-postgresql-7.1.3.tgz", - "integrity": "sha512-MaRNOLbjEDz3VGqP32CGI0DdtvJc/f2OOCYeXkizPCFhFElOxL81qPlN4h9RMVoFuLF7AYdxgGQhev6sOv2psw==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/loopback-connector-postgresql/-/loopback-connector-postgresql-7.1.5.tgz", + "integrity": "sha512-WKKd5rXSEu5GorEUJjIx/Q35qjHhidlu2e/hwudu7ha4k2zGeKnFBbE2fS5RUB5iC/2u+dG23VPl4B4s/KdNKg==", "dependencies": { "async": "^3.2.0", "bluebird": "^3.4.6", @@ -11845,13 +11851,13 @@ } }, "node_modules/loopback-connector-postgresql/node_modules/loopback-connector": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.7.tgz", - "integrity": "sha512-Mc5EkBjOfiCu2d+xlS094oIT8BaT50IohJeOCGB55Gj8iM6EWZOthhEtID0IUHq+7v+/okkf6OhvqILybIIyMw==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.9.tgz", + "integrity": "sha512-Ii4CAHnY4ChKj/2bsiV2Ck59QWcYHTE2HbU3028D2nFWg7N5SAdkkjnhdvqUhzvdt+Vpb0WlEgaR4SKASHqXEw==", "dependencies": { - "async": "^3.2.5", + "async": "^3.2.6", "bluebird": "^3.7.2", - "debug": "^4.3.5", + "debug": "^4.3.7", "msgpack5": "^4.5.1", "strong-globalize": "^6.0.6", "uuid": "^10.0.0" @@ -11907,20 +11913,20 @@ } }, "node_modules/loopback-datasource-juggler": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/loopback-datasource-juggler/-/loopback-datasource-juggler-5.0.11.tgz", - "integrity": "sha512-qPgJA4VbrsclgesjPMkuzCpa+emuVTrmgY89zO7zmWvLnBzCPkIISxhoCAIo6Z3n6VSXkgzNKcstj7lwgbqXhw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/loopback-datasource-juggler/-/loopback-datasource-juggler-5.1.0.tgz", + "integrity": "sha512-oHQ6S4r8/65tR5JfjjDke7Z9QdbBXO21/iy2LqkmvHTGPTa3A61r8mwTOGQtDHpVRJijt+eDHbrufVLzITZEsw==", "dependencies": { - "async": "^3.2.5", + "async": "^3.2.6", "change-case": "^4.1.2", - "debug": "^4.3.5", + "debug": "^4.3.7", "depd": "^2.0.0", "inflection": "^3.0.0", "lodash": "^4.17.21", - "loopback-connector": "^6.1.7", - "minimatch": "^9.0.5", + "loopback-connector": "^6.1.9", + "minimatch": "^10.0.1", "nanoid": "^3.3.7", - "qs": "^6.12.2", + "qs": "^6.13.0", "strong-globalize": "^6.0.6", "traverse": "^0.6.9", "uuid": "^10.0.0" @@ -11938,13 +11944,13 @@ } }, "node_modules/loopback-datasource-juggler/node_modules/loopback-connector": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.7.tgz", - "integrity": "sha512-Mc5EkBjOfiCu2d+xlS094oIT8BaT50IohJeOCGB55Gj8iM6EWZOthhEtID0IUHq+7v+/okkf6OhvqILybIIyMw==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/loopback-connector/-/loopback-connector-6.1.9.tgz", + "integrity": "sha512-Ii4CAHnY4ChKj/2bsiV2Ck59QWcYHTE2HbU3028D2nFWg7N5SAdkkjnhdvqUhzvdt+Vpb0WlEgaR4SKASHqXEw==", "dependencies": { - "async": "^3.2.5", + "async": "^3.2.6", "bluebird": "^3.7.2", - "debug": "^4.3.5", + "debug": "^4.3.7", "msgpack5": "^4.5.1", "strong-globalize": "^6.0.6", "uuid": "^10.0.0" @@ -11953,6 +11959,20 @@ "node": ">=18" } }, + "node_modules/loopback-datasource-juggler/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/loopback4-authentication": { "version": "12.0.2", "resolved": "https://registry.npmjs.org/loopback4-authentication/-/loopback4-authentication-12.0.2.tgz", @@ -12010,6 +12030,25 @@ "node": ">=18" } }, + "node_modules/loopback4-billing": { + "version": "0.0.1", + "resolved": "file:services/subscription-service/loopback4-billing-0.0.1.tgz", + "integrity": "sha512-RO0ja3BgaNvp/SCOwuSfcl3GYofpdC3RCxgin9f4KNwuh6PqSeWGbewJ6/f8aQ3cb6xj13AlwylO6OavWnt1OQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@loopback/rest": "^14.0.0", + "@loopback/rest-explorer": "^7.0.0", + "chargebee": "^2.38.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@loopback/core": "^6.0.0" + } + }, "node_modules/loopback4-dynamic-datasource": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/loopback4-dynamic-datasource/-/loopback4-dynamic-datasource-2.0.1.tgz", @@ -12452,9 +12491,12 @@ "dev": true }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -12479,9 +12521,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { "braces": "^3.0.3", @@ -12766,9 +12808,9 @@ } }, "node_modules/mocha": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.0.tgz", - "integrity": "sha512-v8/rBWr2VO5YkspYINnvu81inSz2y3ODJrhO175/Exzor1RcEZZkizgE2A+w/CAXXoESS8Kys5E62dOHGHzULA==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "dev": true, "dependencies": { "ansi-colors": "^4.1.3", @@ -12843,12 +12885,6 @@ "node": ">=10" } }, - "node_modules/mocha/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -13082,9 +13118,9 @@ } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/msgpack-lite": { "version": "0.1.26", @@ -13101,12 +13137,6 @@ "msgpack": "bin/msgpack" } }, - "node_modules/msgpack-lite/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, "node_modules/msgpack5": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/msgpack5/-/msgpack5-4.5.1.tgz", @@ -13118,11 +13148,6 @@ "safe-buffer": "^5.1.2" } }, - "node_modules/msgpack5/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" - }, "node_modules/msgpack5/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -13264,16 +13289,34 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" }, "node_modules/nise": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nise/-/nise-6.0.0.tgz", - "integrity": "sha512-K8ePqo9BFvN31HXwEtTNGzgrPpmvgciDsFz8aztFjt4LqKO/JeFD8tBOeuDiCMXrIl/m1YvfH8auSpxfaD09wg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-6.1.1.tgz", + "integrity": "sha512-aMSAzLVY7LyeM60gvBS423nBmIPP+Wy7St7hsb+8/fc1HmeoHJfLO8CKse4u3BtOZvQLJghYPI2i/1WZrEj5/g==", "dev": true, "dependencies": { - "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^11.2.2", - "@sinonjs/text-encoding": "^0.7.2", + "@sinonjs/commons": "^3.0.1", + "@sinonjs/fake-timers": "^13.0.1", + "@sinonjs/text-encoding": "^0.7.3", "just-extend": "^6.2.0", - "path-to-regexp": "^6.2.1" + "path-to-regexp": "^8.1.0" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.2.tgz", + "integrity": "sha512-4Bb+oqXZTSTZ1q27Izly9lv8B9dlV61CROxPiVtywwzv5SnytJqhvYe6FclHYuXml4cd1VHPo1zd5PmTeJozvA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "engines": { + "node": ">=16" } }, "node_modules/no-case": { @@ -13366,9 +13409,9 @@ } }, "node_modules/node-gyp-build": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", - "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "version": "4.8.2", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.2.tgz", + "integrity": "sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==", "dev": true, "bin": { "node-gyp-build": "bin.js", @@ -15146,9 +15189,9 @@ } }, "node_modules/package-json-from-dist": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", - "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==" }, "node_modules/pacote": { "version": "15.2.0", @@ -15604,9 +15647,9 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "node_modules/path-to-regexp": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.2.tgz", - "integrity": "sha512-GQX3SSMokngb36+whdpRXE+3f9V8UzyAorlYvOGx87ufGHehNTn5lCxrKtLyZ4Yl/wEKnNnr98ZzOwwDZV5ogw==" + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" }, "node_modules/path-type": { "version": "4.0.0", @@ -15641,13 +15684,13 @@ "peer": true }, "node_modules/pg": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.12.0.tgz", - "integrity": "sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ==", + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.0.tgz", + "integrity": "sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==", "dependencies": { - "pg-connection-string": "^2.6.4", - "pg-pool": "^3.6.2", - "pg-protocol": "^1.6.1", + "pg-connection-string": "^2.7.0", + "pg-pool": "^3.7.0", + "pg-protocol": "^1.7.0", "pg-types": "^2.1.0", "pgpass": "1.x" }, @@ -15673,9 +15716,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.4.tgz", - "integrity": "sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz", + "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==" }, "node_modules/pg-int8": { "version": "1.0.1", @@ -15694,17 +15737,17 @@ } }, "node_modules/pg-pool": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.2.tgz", - "integrity": "sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg==", + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz", + "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==", "peerDependencies": { "pg": ">=8.0" } }, "node_modules/pg-protocol": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.1.tgz", - "integrity": "sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg==" + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz", + "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==" }, "node_modules/pg-types": { "version": "4.0.2", @@ -15790,9 +15833,9 @@ } }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true }, "node_modules/picomatch": { @@ -15937,9 +15980,9 @@ "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==" }, "node_modules/postman-request": { - "version": "2.88.1-postman.37", - "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.37.tgz", - "integrity": "sha512-TpHeMnvO5xvlYCYp8QntLR1Fq0hohWGOLbf9RBqO5JTMdPWZpGBbR8xs11tHsZRVMDXWFg4m960ItkcDxiaWSA==", + "version": "2.88.1-postman.40", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.40.tgz", + "integrity": "sha512-uE4AiIqhjtHKp4pj9ei7fkdfNXEX9IqDBlK1plGAQne6y79UUlrTdtYLhwXoO0AMOvqyl9Ar+BU6Eo6P/MPgfg==", "peer": true, "dependencies": { "@postman/form-data": "~3.1.1", @@ -16198,9 +16241,9 @@ "dev": true }, "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", + "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -17018,6 +17061,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-array-concat/node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -17059,9 +17107,9 @@ } }, "node_modules/safe-stable-stringify": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", - "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "engines": { "node": ">=10" } @@ -17098,9 +17146,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -17133,6 +17181,14 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -17144,11 +17200,6 @@ "node": ">=4" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/sentence-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", @@ -17169,14 +17220,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -17547,13 +17598,13 @@ } }, "node_modules/sinon": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.0.tgz", - "integrity": "sha512-+dXDXzD1sBO6HlmZDd7mXZCR/y5ECiEiGCBSGuFD/kZ0bDTofPYc6JaeGmPSF+1j1MejGUWkORbYOLDyvqCWpA==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-18.0.1.tgz", + "integrity": "sha512-a2N2TDY1uGviajJ6r4D1CyRAkzE9NNVlYOV1wX5xQDuAk0ONgzgRl0EjCQuRCPxOwp13ghsMwt9Gdldujs39qw==", "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/fake-timers": "11.2.2", "@sinonjs/samsam": "^8.0.0", "diff": "^5.2.0", "nise": "^6.0.0", @@ -17805,9 +17856,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.18", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", - "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", "dev": true }, "node_modules/split": { @@ -17910,12 +17961,6 @@ "node": ">=8" } }, - "node_modules/stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "deprecated": "Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility" - }, "node_modules/stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -18123,12 +18168,12 @@ } }, "node_modules/strong-error-handler": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/strong-error-handler/-/strong-error-handler-5.0.10.tgz", - "integrity": "sha512-bSjeWSHizlsefxweVeFv2Pha7z78XL1EVmuMSBkypjBJyYgHgvrqFgjwmKiCBTYt9C15xNnJjUfkRVkuSZTyNw==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/strong-error-handler/-/strong-error-handler-5.0.11.tgz", + "integrity": "sha512-OVvvpTHg3BmNWBsg3St33ADHqBPSRAsAVlwIUyBFNib0fXuY5dCjKyuJAiT2LW9Y1DB182xRFkt6IblcwTbBbw==", "dependencies": { "accepts": "^1.3.8", - "debug": "^4.3.4", + "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", "handlebars": "^4.7.8", "http-status": "^1.7.4", @@ -18259,6 +18304,61 @@ "prom-client": ">= 10 <= 14" } }, + "node_modules/swagger-stats/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/swagger-stats/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/swagger-stats/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/swagger-stats/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/swagger-stats/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/swagger-stats/node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -18642,9 +18742,9 @@ "dev": true }, "node_modules/traverse": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", - "integrity": "sha512-7bBrcF+/LQzSgFmT0X5YclVqQxtv7TDJ1f8Wj7ibBu/U6BMLeOpUxuZjV7rMc44UtKxlnMFigdhFAIszSX1DMg==", + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.10.tgz", + "integrity": "sha512-hN4uFRxbK+PX56DxYiGHsTn2dME3TVr9vbNqlQGcGcPhJAn+tdP126iA+TArMpI4YSgnTkMWyoLl5bf81Hi5TA==", "dependencies": { "gopd": "^1.0.1", "typedarray.prototype.slice": "^1.0.3", @@ -18771,9 +18871,9 @@ } }, "node_modules/tslib": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", - "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==" + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/tsscmp": { "version": "1.0.6", @@ -19148,9 +19248,9 @@ } }, "node_modules/uglify-js": { - "version": "3.19.1", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.1.tgz", - "integrity": "sha512-y/2wiW+ceTYR2TSSptAhfnEtpLaQ4Ups5zrjB2d3kuVxHj16j/QJwPl5PvuGy9uARb39J0+iKxcRPvtpsx4A4A==", + "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==", "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" @@ -19201,9 +19301,9 @@ } }, "node_modules/undici-types": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", - "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==" + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, "node_modules/unicode-properties": { "version": "1.4.1", @@ -19281,9 +19381,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", - "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -19300,8 +19400,8 @@ } ], "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -19561,9 +19661,9 @@ } }, "node_modules/winston": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.1.tgz", - "integrity": "sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.14.2.tgz", + "integrity": "sha512-CO8cdpBB2yqzEf8v895L+GNKYJiEq8eKlHU38af3snQBQ+sdAIUepjMSguOIJC7ICbzm0ZI+Af2If4vIJrtmOg==", "dependencies": { "@colors/colors": "^1.6.0", "@dabh/diagnostics": "^2.0.2", @@ -20028,108 +20128,19 @@ } }, "services/orchestrator-service/node_modules/@types/node": { - "version": "18.19.43", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.43.tgz", - "integrity": "sha512-Mw/YlgXnyJdEwLoFv2dpuJaDFriX+Pc+0qOBJ57jC1H6cDxIj2xc5yUrdtArDVG0m+KV6622a4p2tenEqB3C/g==", + "version": "18.19.53", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.53.tgz", + "integrity": "sha512-GLxgUgHhDKO1Edw9Q0lvMbiO/IQXJwJlMaqxSGBXMpPy8uhkCs2iiPFaB2Q/gmobnFkckD3rqTBMVjXdwq+nKg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" } }, - "services/orchestrator-service/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "services/orchestrator-service/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "services/orchestrator-service/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, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "services/orchestrator-service/node_modules/nodemon": { - "version": "3.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "services/orchestrator-service/node_modules/semver": { - "version": "7.6.3", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "services/orchestrator-service/node_modules/simple-update-notifier": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "services/orchestrator-service/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "services/orchestrator-service/node_modules/typescript": { "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, - "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20153,7 +20164,7 @@ "@loopback/context": "^7.0.2", "@loopback/core": "^6.0.2", "@loopback/openapi-v3": "^10.0.2", - "@loopback/repository": "^7.0.2", + "@loopback/repository": "^7.0.4", "@loopback/rest": "^14.0.2", "@loopback/rest-explorer": "^7.0.2", "@loopback/service-proxy": "^7.0.2", @@ -20173,6 +20184,7 @@ "loopback-connector-postgresql": "^7.1.1", "loopback4-authentication": "^12.0.2", "loopback4-authorization": "^7.0.2", + "loopback4-billing": "file:loopback4-billing-0.0.1.tgz", "swagger-stats": "^0.99.5", "symlink-resolver": "0.2.1", "tslib": "^2.6.2" @@ -20195,9 +20207,10 @@ } }, "services/subscription-service/node_modules/@types/node": { - "version": "18.19.31", + "version": "18.19.53", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.53.tgz", + "integrity": "sha512-GLxgUgHhDKO1Edw9Q0lvMbiO/IQXJwJlMaqxSGBXMpPy8uhkCs2iiPFaB2Q/gmobnFkckD3rqTBMVjXdwq+nKg==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } @@ -20263,10 +20276,12 @@ "@loopback/build": "^11.0.2", "@loopback/eslint-config": "^15.0.2", "@loopback/testlab": "^7.0.2", + "@types/auth0": "^3.3.10", "@types/jsonwebtoken": "^9.0.5", "@types/moment": "^2.13.0", "@types/node": "^18.11.9", "@types/pdfkit": "^0.13.4", + "auth0": "^4.10.0", "eslint": "^8.57.0", "nodemon": "^2.0.21", "nyc": "^15.1.0", @@ -20278,9 +20293,10 @@ } }, "services/tenant-management-service/node_modules/@types/node": { - "version": "18.19.31", + "version": "18.19.53", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.53.tgz", + "integrity": "sha512-GLxgUgHhDKO1Edw9Q0lvMbiO/IQXJwJlMaqxSGBXMpPy8uhkCs2iiPFaB2Q/gmobnFkckD3rqTBMVjXdwq+nKg==", "dev": true, - "license": "MIT", "dependencies": { "undici-types": "~5.26.4" } diff --git a/services/subscription-service/.gitignore b/services/subscription-service/.gitignore index 162c24c..fc51452 100644 --- a/services/subscription-service/.gitignore +++ b/services/subscription-service/.gitignore @@ -49,7 +49,7 @@ typings/ .node_repl_history # Output of 'npm pack' -*.tgz +# *.tgz # Yarn Integrity file .yarn-integrity diff --git a/services/subscription-service/loopback4-billing-0.0.1.tgz b/services/subscription-service/loopback4-billing-0.0.1.tgz new file mode 100644 index 0000000..81152cf Binary files /dev/null and b/services/subscription-service/loopback4-billing-0.0.1.tgz differ diff --git a/services/subscription-service/migrations/pg/migrations/20240209122448-add-customer-table.js b/services/subscription-service/migrations/pg/migrations/20240209122448-add-customer-table.js new file mode 100644 index 0000000..17c1fd9 --- /dev/null +++ b/services/subscription-service/migrations/pg/migrations/20240209122448-add-customer-table.js @@ -0,0 +1,59 @@ +'use strict'; + +let dbm; +let type; +let seed; +let fs = require('fs'); +let path = require('path'); +let Promise; + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function (options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; + Promise = options.Promise; +}; + +exports.up = function (db) { + let filePath = path.join( + __dirname, + 'sqls', + '20240209122448-add-customer-table-up.sql', + ); + return new Promise(function (resolve, reject) { + fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) { + if (err) return reject(err); + console.log('received data: ' + data); + + resolve(data); + }); + }).then(function (data) { + return db.runSql(data); + }); +}; + +exports.down = function (db) { + let filePath = path.join( + __dirname, + 'sqls', + '20240209122448-add-customer-table-down.sql', + ); + return new Promise(function (resolve, reject) { + fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) { + if (err) return reject(err); + console.log('received data: ' + data); + + resolve(data); + }); + }).then(function (data) { + return db.runSql(data); + }); +}; + +exports._meta = { + version: 1, +}; diff --git a/services/subscription-service/migrations/pg/migrations/sqls/20240205055601-init-up.sql b/services/subscription-service/migrations/pg/migrations/sqls/20240205055601-init-up.sql index f5da7f9..4c2261f 100644 --- a/services/subscription-service/migrations/pg/migrations/sqls/20240205055601-init-up.sql +++ b/services/subscription-service/migrations/pg/migrations/sqls/20240205055601-init-up.sql @@ -21,6 +21,8 @@ CREATE TABLE plans ( CONSTRAINT pk_plans_id PRIMARY KEY ( id ) ); + + CREATE TABLE subscriptions ( id uuid DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL , created_on timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL , diff --git a/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-down.sql b/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-down.sql new file mode 100644 index 0000000..66e21ae --- /dev/null +++ b/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-down.sql @@ -0,0 +1,2 @@ +drop table main.billing_customer; +drop table main.invoice; \ No newline at end of file diff --git a/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-up.sql b/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-up.sql new file mode 100644 index 0000000..7cd5d30 --- /dev/null +++ b/services/subscription-service/migrations/pg/migrations/sqls/20240209122448-add-customer-table-up.sql @@ -0,0 +1,41 @@ + +CREATE TABLE main.billing_customer ( + id uuid DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL, + tenant_id varchar(255) NOT NULL, + customer_id varchar(255) NOT NULL, + payment_source_id varchar(255), + created_on timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + modified_on timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted boolean DEFAULT false NOT NULL, + deleted_on timestamptz, + deleted_by uuid, + created_by uuid NOT NULL, + modified_by uuid, + CONSTRAINT pk_billing_customer_id PRIMARY KEY (id), + CONSTRAINT uq_billing_customer_customer_id UNIQUE (customer_id) +); + + + +CREATE TABLE main.invoice ( + id UUID DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid NOT NULL, + invoice_id VARCHAR(255) NOT NULL, + invoice_status VARCHAR(255), + billing_customer_id uuid NOT NULL, + -- subscription_id uuid, + created_on TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + modified_on TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP NOT NULL, + deleted BOOLEAN DEFAULT false NOT NULL, + deleted_on TIMESTAMPTZ, + deleted_by UUID, + created_by UUID NOT NULL, + modified_by UUID, + CONSTRAINT pk_invoice_id PRIMARY KEY (id), + CONSTRAINT fk_invoice_customer FOREIGN KEY (billing_customer_id) REFERENCES main.billing_customer(id) + + -- CONSTRAINT fk_invoice_subscription FOREIGN KEY (subscription_id) REFERENCES main.subscriptions(id) -- Add this constraint +); + +ALTER TABLE main.subscriptions +ADD COLUMN invoice_id uuid NOT NULL, +ADD CONSTRAINT fk_subscriptions_invoice FOREIGN KEY (invoice_id) REFERENCES main.invoice(id); \ No newline at end of file diff --git a/services/subscription-service/package.json b/services/subscription-service/package.json index a9d1822..4e76816 100644 --- a/services/subscription-service/package.json +++ b/services/subscription-service/package.json @@ -67,7 +67,7 @@ "@loopback/context": "^7.0.2", "@loopback/core": "^6.0.2", "@loopback/openapi-v3": "^10.0.2", - "@loopback/repository": "^7.0.2", + "@loopback/repository": "^7.0.4", "@loopback/rest": "^14.0.2", "@loopback/rest-explorer": "^7.0.2", "@loopback/service-proxy": "^7.0.2", @@ -84,6 +84,7 @@ "@types/jsonwebtoken": "^9.0.5", "dotenv": "^16.0.3", "dotenv-extended": "^2.9.0", + "loopback4-billing": "file:loopback4-billing-0.0.1.tgz", "loopback-connector-postgresql": "^7.1.1", "loopback4-authentication": "^12.0.2", "loopback4-authorization": "^7.0.2", diff --git a/services/subscription-service/sourceloop-ctrl-plane-subscription-service-0.2.1.tgz b/services/subscription-service/sourceloop-ctrl-plane-subscription-service-0.2.1.tgz new file mode 100644 index 0000000..85cab80 Binary files /dev/null and b/services/subscription-service/sourceloop-ctrl-plane-subscription-service-0.2.1.tgz differ diff --git a/services/subscription-service/src/component.ts b/services/subscription-service/src/component.ts index 9455848..4aee3f0 100644 --- a/services/subscription-service/src/component.ts +++ b/services/subscription-service/src/component.ts @@ -2,68 +2,83 @@ // // This software is released under the MIT License. // https://opensource.org/licenses/MIT + +import {inject, Binding} from '@loopback/context'; import { - Binding, Component, - ControllerClass, CoreBindings, - inject, ProviderMap, ServiceOrProviderClass, + ControllerClass, } from '@loopback/core'; -import {Class, Model, Repository} from '@loopback/repository'; +import {Class, Repository, Model} from '@loopback/repository'; import {RestApplication} from '@loopback/rest'; import { - BearerVerifierBindings, - BearerVerifierComponent, - BearerVerifierConfig, - BearerVerifierType, CoreComponent, SECURITY_SCHEME_SPEC, ServiceSequence, + BearerVerifierBindings, + BearerVerifierType, + BearerVerifierConfig, + BearerVerifierComponent, } from '@sourceloop/core'; +import { + FeatureToggleBindings, + FeatureToggleServiceComponent, +} from '@sourceloop/feature-toggle-service'; +import {BillingComponent} from 'loopback4-billing'; import {AuthenticationComponent} from 'loopback4-authentication'; import { AuthorizationBindings, AuthorizationComponent, } from 'loopback4-authorization'; -import {SubscriptionServiceBindings} from './keys'; -import {ISubscriptionServiceConfig} from './types'; -import { - BillingCycleRepository, - CurrencyRepository, - PlanRepository, - PlanSizesRepository, - ResourceRepository, - ServiceRepository, - SubscriptionRepository, -} from './repositories'; import { BillinCycleController, - CurrencyController, HomePageController, PingController, + CurrencyController, PlanController, - PlanFeaturesController, - PlanSizesController, - PlanSubscriptionController, ResourceController, ServiceController, SubscriptionController, + PlanSubscriptionController, + PlanSizesController, + PlanFeaturesController, } from './controllers'; +import { + SubscriptionServiceBindings, + SYSTEM_USER, + WEBHOOK_VERIFIER, +} from './keys'; import { BillingCycle, Currency, Plan, - PlanSizes, Resource, + BillingCustomer, + Invoice, Service, Subscription, + PlanSizes, } from './models'; import { - FeatureToggleBindings, - FeatureToggleServiceComponent, -} from '@sourceloop/feature-toggle-service'; + BillingCycleRepository, + CurrencyRepository, + PlanRepository, + ResourceRepository, + ServiceRepository, + SubscriptionRepository, + PlanSizesRepository, + BillingCustomerRepository, + InvoiceRepository, +} from './repositories'; +import {ISubscriptionServiceConfig} from './types'; +import {WebhookVerifierProvider} from './interceptors/webhook-verifier.interceptor'; +import {SystemUserProvider} from './providers'; +import {BillingCustomerController} from './controllers/billing-customer.controller'; +import {BillingInvoiceController} from './controllers/billing-invoice.controller'; +import {BillingPaymentSourceController} from './controllers/billing-payment-source.controller'; +import {WebhookController} from './controllers/webhook.controller'; export class SubscriptionServiceComponent implements Component { constructor( @@ -82,6 +97,7 @@ export class SubscriptionServiceComponent implements Component { .bind(FeatureToggleBindings.Config) .to({bindControllers: true, useCustomSequence: true}); this.application.component(FeatureToggleServiceComponent); + this.application.component(BillingComponent); this.application.api({ openapi: '3.0.0', @@ -109,6 +125,8 @@ export class SubscriptionServiceComponent implements Component { ServiceRepository, SubscriptionRepository, PlanSizesRepository, + BillingCustomerRepository, + InvoiceRepository, ]; this.models = [ @@ -116,10 +134,17 @@ export class SubscriptionServiceComponent implements Component { Currency, Plan, Resource, + BillingCustomer, + Invoice, Service, Subscription, PlanSizes, ]; + this.bindings = [ + Binding.bind(WEBHOOK_VERIFIER).toProvider(WebhookVerifierProvider), + + Binding.bind(SYSTEM_USER).toProvider(SystemUserProvider), + ]; this.controllers = [ BillinCycleController, @@ -133,6 +158,10 @@ export class SubscriptionServiceComponent implements Component { PlanSubscriptionController, PlanSizesController, PlanFeaturesController, + BillingCustomerController, + BillingInvoiceController, + BillingPaymentSourceController, + WebhookController, ]; } diff --git a/services/subscription-service/src/controllers/billing-customer.controller.ts b/services/subscription-service/src/controllers/billing-customer.controller.ts new file mode 100644 index 0000000..df3ea0e --- /dev/null +++ b/services/subscription-service/src/controllers/billing-customer.controller.ts @@ -0,0 +1,211 @@ +import {BillingComponentBindings, IService} from 'loopback4-billing'; +import {inject} from '@loopback/core'; +import {Filter, repository} from '@loopback/repository'; +import { + del, + get, + getModelSchemaRef, + param, + patch, + post, + requestBody, +} from '@loopback/rest'; +import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core'; +import {authenticate, STRATEGY} from 'loopback4-authentication'; +import {authorize} from 'loopback4-authorization'; +import {AddressDto} from '../models'; +import {BillingCustomer} from '../models/billing-customer.model'; +import {CustomerDto} from '../models/dto/customer-dto.model'; +import {PermissionKey} from '../permissions'; +import {InvoiceRepository} from '../repositories'; +import {BillingCustomerRepository} from '../repositories/billing-customer.repository'; + +const basePath = '/billing-customer'; +export class BillingCustomerController { + constructor( + @repository(BillingCustomerRepository) + public billingCustomerRepository: BillingCustomerRepository, + @repository(InvoiceRepository) + public invoiceRepository: InvoiceRepository, + @inject(BillingComponentBindings.BillingProvider) + private readonly billingProvider: IService, + ) {} + + @authorize({ + permissions: [PermissionKey.CreateBillingCustomer], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'BillingCustomer model instance', + content: { + 'application/json': { + schema: getModelSchemaRef(CustomerDto, { + title: 'NewBillingCustomer', + }), + }, + }, + }, + }, + }) + async create( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(CustomerDto, { + title: 'NewCustomer', + exclude: ['id'], + }), + }, + }, + }) + customerDto: Omit, + @param.header.string('tenantId') tenantId: string, + ): Promise { + const customer = await this.billingProvider.createCustomer(customerDto); + await this.billingCustomerRepository.create( + new BillingCustomer({ + tenantId, + customerId: customer.id, + }), + ); + return new CustomerDto({ + id: customer.id, + firstName: customer.firstName, + lastName: customer.lastName, + email: customer.email, + company: customer.company, + phone: customer.phone, + billingAddress: new AddressDto({ + firstName: customer.billingAddress?.firstName, + lastName: customer.billingAddress?.lastName, + email: customer.billingAddress?.email, + company: customer.billingAddress?.company, + phone: customer.billingAddress?.phone, + city: customer.billingAddress?.city, + state: customer.billingAddress?.state, + zip: customer.billingAddress?.zip, + country: customer.billingAddress?.country, + }), + }); + } + + @authorize({ + permissions: [PermissionKey.GetBillingCustomer], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'BillingCustomer model ', + content: {'application/json': {schema: getModelSchemaRef(CustomerDto)}}, + }, + }, + }) + async getCustomer( + @param.filter(BillingCustomer) filter?: Filter, + ): Promise<{customerDetails: CustomerDto; info: BillingCustomer}> { + const customers = await this.billingCustomerRepository.find(filter); + if (customers.length === 0) { + throw new Error('Customer is not present'); + } + + const customer = await this.billingProvider.getCustomers( + customers[0].customerId, + ); + return { + customerDetails: new CustomerDto({ + firstName: customer.firstName, + lastName: customer.lastName, + email: customer.email, + company: customer.company, + phone: customer.phone, + billingAddress: new AddressDto({ + firstName: customer.billingAddress?.firstName, + lastName: customer.billingAddress?.lastName, + email: customer.billingAddress?.email, + company: customer.billingAddress?.company, + phone: customer.billingAddress?.phone, + city: customer.billingAddress?.city, + state: customer.billingAddress?.state, + zip: customer.billingAddress?.zip, + country: customer.billingAddress?.country, + }), + }), + info: customers[0], + }; + } + + @authorize({ + permissions: [PermissionKey.UpdateBillingCustomer], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @patch(`${basePath}/{tenantId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'BillingCustomer PATCH success', + }, + }, + }) + async updateById( + @param.path.string('tenantId') tenantId: string, + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(CustomerDto, {partial: true}), + }, + }, + }) + customerDto: Partial, + ): Promise { + const customers = await this.billingCustomerRepository.find({ + where: {tenantId: tenantId}, + }); + + if (customers.length === 0) { + throw new Error(`Customer with tenantId ${tenantId} is not present`); + } + await this.billingProvider.updateCustomerById( + customers[0].customerId, + customerDto, + ); + } + + @authorize({ + permissions: [PermissionKey.DeleteBillingCustomer], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @del(`${basePath}/{tenantId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'BillingCustomer DELETE success', + }, + }, + }) + async deleteById( + @param.path.string('tenantId') tenantId: string, + ): Promise { + const customer = await this.billingCustomerRepository.find({ + where: {tenantId: tenantId}, + }); + if (customer.length === 0) { + throw new Error(' Customer with tenantId is not present'); + } + await this.billingProvider.deleteCustomer(customer[0].customerId); + await this.invoiceRepository.deleteAll({billingCustomerId: customer[0].id}); + await this.billingCustomerRepository.deleteById(customer[0].id); + } +} diff --git a/services/subscription-service/src/controllers/billing-invoice.controller.ts b/services/subscription-service/src/controllers/billing-invoice.controller.ts new file mode 100644 index 0000000..83b4f61 --- /dev/null +++ b/services/subscription-service/src/controllers/billing-invoice.controller.ts @@ -0,0 +1,227 @@ +import {BillingComponentBindings, IService} from 'loopback4-billing'; +import {inject} from '@loopback/core'; +import {repository} from '@loopback/repository'; +import { + del, + get, + getModelSchemaRef, + param, + patch, + post, + requestBody, +} from '@loopback/rest'; +import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core'; +import {authenticate, STRATEGY} from 'loopback4-authentication'; +import {authorize} from 'loopback4-authorization'; +import {AddressDto, ChargeDto} from '../models'; +import {InvoiceDto} from '../models/dto/invoice-dto.model'; +import {TransactionDto} from '../models/dto/transaction-dto.model'; +import {PermissionKey} from '../permissions'; +import {InvoiceRepository} from '../repositories'; +import {BillingCustomerRepository} from '../repositories/billing-customer.repository'; + +const basePath = '/billing-invoice'; +export class BillingInvoiceController { + constructor( + @repository(BillingCustomerRepository) + public billingCustomerRepository: BillingCustomerRepository, + @repository(InvoiceRepository) + public invoiceRepository: InvoiceRepository, + @inject(BillingComponentBindings.BillingProvider) + private readonly billingProvider: IService, + ) {} + + @authorize({ + permissions: [PermissionKey.CreateBillingInvoice], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'invoice model instance', + content: {'application/json': {schema: getModelSchemaRef(InvoiceDto)}}, + }, + }, + }) + async create( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(InvoiceDto, { + title: 'newInvoice', + exclude: ['id', 'status'], + }), + }, + }, + }) + invoiceDto: Omit, + ): Promise { + const customer = await this.billingCustomerRepository.find({ + where: {customerId: invoiceDto.customerId}, + }); + + if (customer.length === 0) { + throw new Error(' Customer with tenantId is not present'); + } + const invoice = await this.billingProvider.createInvoice(invoiceDto); + const charges = invoice.charges?.map( + charge => + new ChargeDto({amount: charge.amount, description: charge.description}), + ); + + const invoiceInfo = await this.invoiceRepository.create({ + invoiceId: invoice.id, + invoiceStatus: invoice.status, + billingCustomerId: customer[0].id, + }); + return new InvoiceDto({ + id: invoiceInfo.id, // passed the id of invoice info created in our db, to setup relation between subscription and invoice + customerId: invoice.customerId, + charges: charges, + status: invoice.status, + shippingAddress: new AddressDto({ + firstName: invoice.shippingAddress?.firstName ?? '', + lastName: invoice.shippingAddress?.lastName ?? '', + email: invoice.shippingAddress?.email ?? '', + company: invoice.shippingAddress?.company, + phone: invoice.shippingAddress?.phone, + city: invoice.shippingAddress?.city ?? '', + state: invoice.shippingAddress?.state ?? '', + zip: invoice.shippingAddress?.zip ?? '', + country: invoice.shippingAddress?.country ?? '', + }), + options: invoice.options, + }); + } + + @authorize({ + permissions: [PermissionKey.GetBillingInvoice], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(`${basePath}/{invoiceId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'get invoice', + content: {'application/json': {schema: getModelSchemaRef(InvoiceDto)}}, + }, + }, + }) + async getInvoice( + @param.path.string('invoiceId') invoiceId: string, + ): Promise { + const invoice = await this.billingProvider.retrieveInvoice(invoiceId); + const charges = invoice.charges?.map( + charge => + new ChargeDto({amount: charge.amount, description: charge.description}), + ); + return new InvoiceDto({ + customerId: invoice.customerId, + charges: charges, + status: invoice.status, + shippingAddress: new AddressDto({ + firstName: invoice.shippingAddress?.firstName ?? '', + lastName: invoice.shippingAddress?.lastName ?? '', + email: invoice.shippingAddress?.email ?? '', + company: invoice.shippingAddress?.company, + phone: invoice.shippingAddress?.phone, + city: invoice.shippingAddress?.city ?? '', + state: invoice.shippingAddress?.state ?? '', + zip: invoice.shippingAddress?.zip ?? '', + country: invoice.shippingAddress?.country ?? '', + }), + options: invoice.options, + }); + } + + @authorize({ + permissions: [PermissionKey.UpdateBillingInvoice], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @patch(`${basePath}/{invoiceId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Billing Invoice PATCH success', + }, + }, + }) + async updateById( + @param.path.string('invoiceId') invoiceId: string, + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(InvoiceDto, {partial: true}), + }, + }, + }) + invoiceDto: Partial, + ): Promise { + await this.billingProvider.updateInvoice(invoiceId, invoiceDto); + } + + @authorize({ + permissions: [PermissionKey.CreateBillingInvoice], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(`${basePath}/{invoiceId}/payments`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'invoice model instance', + }, + }, + }) + async applyPaymentForInvoice( + @param.path.string('invoiceId') invoiceId: string, + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(TransactionDto, {partial: true}), + }, + }, + }) + transactionDto: TransactionDto, + ): Promise { + const invoiceInfo = await this.invoiceRepository.findById(invoiceId); + await this.billingProvider.applyPaymentSourceForInvoice( + invoiceInfo.invoiceId, + transactionDto, + ); + } + + @authorize({ + permissions: [PermissionKey.DeleteBillingPaymentSource], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @del(`${basePath}/{invoiceId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Billing Invoice DELETE success', + }, + }, + }) + async deleteById( + @param.path.string('invoiceId') invoiceId: string, + ): Promise { + const invoice = await this.invoiceRepository.find({ + where: {invoiceId: invoiceId}, + }); + if (invoice.length === 0) + throw new Error(' invoice with invoiceId is not present'); + await this.billingProvider.deleteInvoice(invoiceId); + await this.invoiceRepository.deleteById(invoice[0].id); + } +} diff --git a/services/subscription-service/src/controllers/billing-payment-source.controller.ts b/services/subscription-service/src/controllers/billing-payment-source.controller.ts new file mode 100644 index 0000000..28fc6c3 --- /dev/null +++ b/services/subscription-service/src/controllers/billing-payment-source.controller.ts @@ -0,0 +1,138 @@ +import {BillingComponentBindings, IService} from 'loopback4-billing'; +import {inject} from '@loopback/core'; +import {repository} from '@loopback/repository'; +import { + del, + get, + getModelSchemaRef, + param, + post, + requestBody, +} from '@loopback/rest'; +import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core'; +import {authenticate, STRATEGY} from 'loopback4-authentication'; +import {authorize} from 'loopback4-authorization'; +import {PaymentSourceDto} from '../models/dto/payment-dto.model'; +import {PermissionKey} from '../permissions'; +import {InvoiceRepository} from '../repositories'; +import {BillingCustomerRepository} from '../repositories/billing-customer.repository'; + +const basePath = '/billing-payment-source'; +export class BillingPaymentSourceController { + constructor( + @repository(BillingCustomerRepository) + public billingCustomerRepository: BillingCustomerRepository, + @repository(InvoiceRepository) + public invoiceRepository: InvoiceRepository, + @inject(BillingComponentBindings.BillingProvider) + private readonly billingProvider: IService, + ) {} + + @authorize({ + permissions: [PermissionKey.CreateBillingPaymentSource], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'Payment model instance', + content: { + 'application/json': {schema: getModelSchemaRef(PaymentSourceDto)}, + }, + }, + }, + }) + async create( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(PaymentSourceDto, { + title: 'NewPaymentSource', + exclude: ['id'], + }), + }, + }, + }) + paymentSourceDto: PaymentSourceDto, + ): Promise { + const customer = await this.billingCustomerRepository.find({ + where: {customerId: paymentSourceDto.customerId}, + }); + + if (customer.length === 0) { + throw new Error(' Customer with tenantId is not present'); + } + const paymentSource = + await this.billingProvider.createPaymentSource(paymentSourceDto); + await this.billingCustomerRepository.updateById(customer[0].id, { + paymentSourceId: paymentSource.id, + }); + return new PaymentSourceDto({ + id: paymentSource.id, + customerId: paymentSource.customerId, + card: paymentSource.card, + }); + } + + @authorize({ + permissions: [PermissionKey.GetBillingPaymentSource], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'get payment source', + content: { + 'application/json': {schema: getModelSchemaRef(PaymentSourceDto)}, + }, + }, + }, + }) + async getPaymentSource( + @param.path.string('paymentSourceId') paymentSourceId: string, + ): Promise { + const paymentSource = + await this.billingProvider.retrievePaymentSource(paymentSourceId); + return new PaymentSourceDto({ + id: paymentSource.id, + customerId: paymentSource.customerId, + card: paymentSource.card, + }); + } + + @authorize({ + permissions: [PermissionKey.DeleteBillingPaymentSource], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @del(`${basePath}/{paymentSourceId}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Billing Payment Source DELETE success', + }, + }, + }) + async deleteById( + @param.path.string('paymentSourceId') paymentSourceId: string, + ): Promise { + const customer = await this.billingCustomerRepository.find({ + where: {paymentSourceId: paymentSourceId}, + }); + + if (customer.length === 0) { + throw new Error(' Customer with tenantId is not present'); + } + await this.billingProvider.deletePaymentSource(paymentSourceId); + await this.billingCustomerRepository.updateById(customer[0].id, { + paymentSourceId: undefined, + }); + } +} diff --git a/services/subscription-service/src/controllers/index.ts b/services/subscription-service/src/controllers/index.ts index 7910a50..4ebd610 100644 --- a/services/subscription-service/src/controllers/index.ts +++ b/services/subscription-service/src/controllers/index.ts @@ -9,3 +9,4 @@ export * from './currency.controller'; export * from './plan-subscription.controller'; export * from './plan-sizes.controller'; export * from './plan-features.controller'; +export * from './subscription-invoice.controller'; diff --git a/services/subscription-service/src/controllers/subscription-invoice.controller.ts b/services/subscription-service/src/controllers/subscription-invoice.controller.ts new file mode 100644 index 0000000..96b541e --- /dev/null +++ b/services/subscription-service/src/controllers/subscription-invoice.controller.ts @@ -0,0 +1,29 @@ +import {repository} from '@loopback/repository'; +import {param, get, getModelSchemaRef} from '@loopback/rest'; +import {Subscription, Invoice} from '../models'; +import {SubscriptionRepository} from '../repositories'; + +export class SubscriptionInvoiceController { + constructor( + @repository(SubscriptionRepository) + public subscriptionRepository: SubscriptionRepository, + ) {} + + @get('/subscriptions/{id}/invoice', { + responses: { + '200': { + description: 'Invoice belonging to Subscription', + content: { + 'application/json': { + schema: getModelSchemaRef(Invoice), + }, + }, + }, + }, + }) + async getInvoice( + @param.path.string('id') id: typeof Subscription.prototype.id, + ): Promise { + return this.subscriptionRepository.invoice(id); + } +} diff --git a/services/subscription-service/src/controllers/webhook.controller.ts b/services/subscription-service/src/controllers/webhook.controller.ts new file mode 100644 index 0000000..aa97d67 --- /dev/null +++ b/services/subscription-service/src/controllers/webhook.controller.ts @@ -0,0 +1,36 @@ +import {intercept} from '@loopback/core'; +import {repository} from '@loopback/repository'; +import {post, requestBody} from '@loopback/rest'; +import {authorize} from 'loopback4-authorization'; +import {WEBHOOK_VERIFIER} from '../keys'; +import {InvoiceRepository} from '../repositories'; +import {BillingCustomerRepository} from '../repositories/billing-customer.repository'; +import {IContent, IPayload} from '../types'; + +export class WebhookController { + constructor( + @repository(BillingCustomerRepository) + public billingCustomerRepository: BillingCustomerRepository, + @repository(InvoiceRepository) + public invoiceRepository: InvoiceRepository, + ) {} + + @authorize({ + permissions: ['*'], + }) + @intercept(WEBHOOK_VERIFIER) + @post('/webhooks/billing-payment') + async handleWebhook(@requestBody() payload: IPayload): Promise { + const content = payload.content; + await this.handlePayment(content); + } + + private async handlePayment(content: IContent): Promise { + const invoice = await this.invoiceRepository.find({ + where: {invoiceId: content.invoice.id}, + }); + await this.invoiceRepository.updateById(invoice[0].id, { + invoiceStatus: content.invoice.status, + }); + } +} diff --git a/services/subscription-service/src/interceptors/index.ts b/services/subscription-service/src/interceptors/index.ts new file mode 100644 index 0000000..dc511f6 --- /dev/null +++ b/services/subscription-service/src/interceptors/index.ts @@ -0,0 +1 @@ +export * from './webhook-verifier.interceptor'; diff --git a/services/subscription-service/src/interceptors/webhook-verifier.interceptor.ts b/services/subscription-service/src/interceptors/webhook-verifier.interceptor.ts new file mode 100644 index 0000000..26a204e --- /dev/null +++ b/services/subscription-service/src/interceptors/webhook-verifier.interceptor.ts @@ -0,0 +1,51 @@ +import { + Interceptor, + InvocationContext, + Provider, + Setter, + ValueOrPromise, + inject, +} from '@loopback/core'; +import {HttpErrors, RequestContext} from '@loopback/rest'; +import {ILogger, LOGGER} from '@sourceloop/core'; +import {AuthenticationBindings, IAuthUser} from 'loopback4-authentication'; +import {SYSTEM_USER} from '../keys'; + +export class WebhookVerifierProvider implements Provider { + constructor( + @inject(LOGGER.LOGGER_INJECT) + private readonly logger: ILogger, + @inject.setter(AuthenticationBindings.CURRENT_USER) + private readonly setCurrentUser: Setter, + @inject(SYSTEM_USER) + private readonly systemUser: IAuthUser, + ) {} + + value() { + return this.intercept.bind(this); + } + + async intercept( + invocationCtx: InvocationContext, + next: () => ValueOrPromise, + ) { + const {request} = invocationCtx.parent as RequestContext; + const authHeader = request.headers['authorization']; + const username = process.env.WEBHOOK_USERNAME; + const password = process.env.WEBHOOK_PASSWORD; + const expectedAuthHeader = + 'Basic ' + Buffer.from(`${username}:${password}`).toString('base64'); + + try { + if (!authHeader || authHeader !== expectedAuthHeader) { + throw new HttpErrors.Unauthorized('Invalid authorization.'); + } + } catch (e) { + this.logger.error(e); + throw new HttpErrors.Unauthorized(); + } + + this.setCurrentUser(this.systemUser); + return next(); + } +} diff --git a/services/subscription-service/src/keys.ts b/services/subscription-service/src/keys.ts index 3801d39..6c541cf 100644 --- a/services/subscription-service/src/keys.ts +++ b/services/subscription-service/src/keys.ts @@ -1,8 +1,9 @@ -import {BindingKey} from '@loopback/core'; +import {BindingKey, Interceptor} from '@loopback/core'; import {ISubscriptionServiceConfig} from './types'; import {BINDING_PREFIX} from '@sourceloop/core'; import {VerifyFunction} from 'loopback4-authentication'; import {AnyObject} from '@loopback/repository'; +import {IAuthUser} from 'loopback4-authorization'; export namespace SubscriptionServiceBindings { export const Config = BindingKey.create( @@ -10,9 +11,20 @@ export namespace SubscriptionServiceBindings { ); } +export const WEBHOOK_VERIFIER = BindingKey.create( + 'sf.webhook.verifier', +); + +/** + * Binding key for the lead token verifier. + */ export const LEAD_TOKEN_VERIFIER = BindingKey.create< VerifyFunction.BearerFn >('sf.user.lead.verifier'); + /** - * Binding key for the lead token verifier. + * Binding key for the system user. */ +export const SYSTEM_USER = BindingKey.create( + 'sf.user.system', +); diff --git a/services/subscription-service/src/models/billing-customer.model.ts b/services/subscription-service/src/models/billing-customer.model.ts new file mode 100644 index 0000000..47bc964 --- /dev/null +++ b/services/subscription-service/src/models/billing-customer.model.ts @@ -0,0 +1,44 @@ +import {model, property, hasMany} from '@loopback/repository'; +import {UserModifiableEntity} from '@sourceloop/core'; +import {Invoice} from './invoice.model'; + +@model({ + name: 'billing_customer', + description: 'contacts belonging to a tenant', +}) +export class BillingCustomer extends UserModifiableEntity { + @property({ + type: 'string', + id: true, + generated: true, + }) + id?: string; + + @property({ + type: 'string', + name: 'tenant_id', + required: true, + }) + tenantId: string; // tenantId of customer + + @property({ + type: 'string', + name: 'customer_id', + required: true, + }) + customerId: string; // id of customer generated on third party billing module + + @property({ + type: 'string', + name: 'payment_source_id', + }) + paymentSourceId?: string; + + // Define the hasMany relation + @hasMany(() => Invoice, {keyTo: 'billingCustomerId'}) + invoices: Invoice[]; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/subscription-service/src/models/dto/address-dto.model.ts b/services/subscription-service/src/models/dto/address-dto.model.ts new file mode 100644 index 0000000..248ea5c --- /dev/null +++ b/services/subscription-service/src/models/dto/address-dto.model.ts @@ -0,0 +1,66 @@ +import {Entity, model, property} from '@loopback/repository'; +import {Options} from 'loopback4-billing'; + +@model() +export class AddressDto extends Entity { + @property({ + type: 'string', + name: 'first_name', + }) + firstName: string; + + @property({ + type: 'string', + name: 'last_name', + }) + lastName: string; + + @property({ + type: 'string', + required: true, + }) + email: string; + + @property({ + type: 'string', + }) + company?: string; + + @property({ + type: 'string', + }) + phone?: string; + + @property({ + type: 'string', + required: true, + }) + city: string; + + @property({ + type: 'string', + required: true, + }) + state: string; + + @property({ + type: 'string', + required: true, + }) + zip: string; + + @property({ + type: 'string', + required: true, + }) + country: string; + + @property({ + type: 'object', + }) + options?: Options; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/subscription-service/src/models/dto/charge-dto.model.ts b/services/subscription-service/src/models/dto/charge-dto.model.ts new file mode 100644 index 0000000..d17f0c2 --- /dev/null +++ b/services/subscription-service/src/models/dto/charge-dto.model.ts @@ -0,0 +1,20 @@ +import {Entity, model, property} from '@loopback/repository'; + +@model() +export class ChargeDto extends Entity { + @property({ + type: 'number', + required: true, + }) + amount: number; + + @property({ + type: 'string', + required: true, + }) + description: string; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/subscription-service/src/models/dto/customer-dto.model.ts b/services/subscription-service/src/models/dto/customer-dto.model.ts new file mode 100644 index 0000000..d00de82 --- /dev/null +++ b/services/subscription-service/src/models/dto/customer-dto.model.ts @@ -0,0 +1,75 @@ +import {model, Model, property} from '@loopback/repository'; +import {AddressDto} from './address-dto.model'; +import {Options} from 'loopback4-billing'; + +@model({ + name: 'customer_dto', +}) +export class CustomerDto extends Model { + @property({ + type: 'string', + name: 'id', + }) + id?: string; + + @property({ + type: 'string', + name: 'first_name', + }) + firstName: string; + + @property({ + type: 'string', + name: 'last_name', + }) + lastName: string; + + @property({ + type: 'string', + name: 'email', + }) + email: string; + + @property({ + type: 'string', + name: 'company', + }) + company: string; + + @property({ + type: 'string', + name: 'phone', + }) + phone: string; + + @property({ + type: AddressDto, + name: 'billing_address', + }) + billingAddress: AddressDto; + + @property({ + type: 'object', + }) + options?: Options; + + constructor(data?: Partial) { + super(data); + } +} + +// this refers to the billing address and shipping address. +export interface IAddress { + firstName: string; + lastName: string; + email: string; + company?: string; + phone?: string; + line1?: string; + line2?: string; + line3?: string; + city: string; + state: string; + zip: string; + coutnry: string; +} diff --git a/services/subscription-service/src/models/dto/index.ts b/services/subscription-service/src/models/dto/index.ts new file mode 100644 index 0000000..3037b0e --- /dev/null +++ b/services/subscription-service/src/models/dto/index.ts @@ -0,0 +1,5 @@ +export * from './address-dto.model'; +export * from './charge-dto.model'; +export * from './customer-dto.model'; +export * from './invoice-dto.model'; +export * from './payment-dto.model'; diff --git a/services/subscription-service/src/models/dto/invoice-dto.model.ts b/services/subscription-service/src/models/dto/invoice-dto.model.ts new file mode 100644 index 0000000..a1803f0 --- /dev/null +++ b/services/subscription-service/src/models/dto/invoice-dto.model.ts @@ -0,0 +1,50 @@ +import {model, Model, property} from '@loopback/repository'; +import {Options} from 'loopback4-billing'; +import {AddressDto} from './address-dto.model'; +import {ChargeDto} from './charge-dto.model'; +import {InvoiceStatus} from '../../types'; + +@model({ + name: 'invoice_dto', +}) +export class InvoiceDto extends Model { + @property({ + type: 'string', + name: 'id', + }) + id?: string; + + @property({ + type: 'string', + name: 'customer_id', + }) + customerId: string; + + @property({ + type: 'object', + }) + options?: Options; + + @property({ + type: AddressDto, + name: 'shipping_address', + }) + shippingAddress: AddressDto; + + @property({ + type: 'array', + itemType: ChargeDto, + name: 'charges', + }) + charges: ChargeDto[]; + + @property({ + type: 'string', + name: 'status', + }) + status?: InvoiceStatus; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/subscription-service/src/models/dto/payment-dto.model.ts b/services/subscription-service/src/models/dto/payment-dto.model.ts new file mode 100644 index 0000000..af719db --- /dev/null +++ b/services/subscription-service/src/models/dto/payment-dto.model.ts @@ -0,0 +1,55 @@ +import {model, Model, property} from '@loopback/repository'; + +@model({ + name: 'payment_source_dto', +}) +export class PaymentSourceDto extends Model { + @property({ + type: 'string', + name: 'id', + }) + id?: string; + + @property({ + type: 'string', + name: 'customer_id', + }) + customerId: string; + + @property({ + type: 'object', + name: 'card', + required: true, + jsonSchema: { + type: 'object', + properties: { + gatewayAccountId: {type: 'string'}, + number: {type: 'string'}, + expiryMonth: {type: 'number'}, + expiryear: {type: 'number'}, + cvv: {type: 'string'}, + }, + required: [ + 'gatewayAccountId', + 'number', + 'expiryMonth', + 'expiryYear', + 'cvv', + ], + }, + }) + card: ICard; + + constructor(data?: Partial) { + super(data); + } +} + +// this refers to the card +export interface ICard { + gatewayAccountId: string; + number: string; + expiryMonth: number; + expiryYear: number; + cvv: string; +} diff --git a/services/subscription-service/src/models/dto/transaction-dto.model.ts b/services/subscription-service/src/models/dto/transaction-dto.model.ts new file mode 100644 index 0000000..eb5cef8 --- /dev/null +++ b/services/subscription-service/src/models/dto/transaction-dto.model.ts @@ -0,0 +1,110 @@ +import {Entity, model, property} from '@loopback/repository'; +export enum PaymentMethodEnum { + Cash = 'cash', + Check = 'check', + BankTranser = 'bank_transfer', + Other = 'other', + Custom = 'custom', + PaymentSource = 'payment_source', +} + +@model() +export class TransactionDto extends Entity { + @property({ + type: 'number', + required: false, + jsonSchema: { + minimum: 0, + }, + }) + amount?: number; + + @property({ + type: 'string', + description: 'payment method', + required: true, + jsonSchema: { + enum: Object.values(PaymentMethodEnum), + }, + }) + paymentMethod: PaymentMethodEnum; + + @property({ + type: 'string', + required: false, + }) + paymentSourceId?: string; // Optional + + @property({ + type: 'string', + required: false, + jsonSchema: { + maxLength: 100, + }, + }) + referenceNumber?: string; // Optional, max 100 chars + + @property({ + type: 'string', + required: false, + jsonSchema: { + maxLength: 50, + }, + }) + customPaymentMethodId?: string; // Optional, max 50 chars + + @property({ + type: 'string', + required: false, + jsonSchema: { + maxLength: 100, + }, + }) + idAtGateway?: string; // Optional, max 100 chars + + @property({ + type: 'string', + required: false, + jsonSchema: { + enum: ['success', 'failure'], + }, + }) + status?: 'success' | 'failure'; // Optional + + @property({ + type: 'number', + required: false, + }) + date?: number; // Optional, timestamp in seconds (UTC) + + @property({ + type: 'string', + required: false, + jsonSchema: { + maxLength: 100, + }, + }) + errorCode?: string; // Optional, max 100 chars + + @property({ + type: 'string', + required: false, + jsonSchema: { + maxLength: 65000, + }, + }) + errorText?: string; // Optional, max 65k chars + + @property({ + type: 'string', + required: false, + jsonSchema: { + maxLength: 300, + }, + }) + comment?: string; // Optional, max 300 chars + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/subscription-service/src/models/index.ts b/services/subscription-service/src/models/index.ts index 0dc32c6..8753b60 100644 --- a/services/subscription-service/src/models/index.ts +++ b/services/subscription-service/src/models/index.ts @@ -5,3 +5,6 @@ export * from './resource.model'; export * from './billing-cycle.model'; export * from './currency.model'; export * from './plan-sizes.model'; +export * from './dto'; +export * from './billing-customer.model'; +export * from './invoice.model'; diff --git a/services/subscription-service/src/models/invoice.model.ts b/services/subscription-service/src/models/invoice.model.ts new file mode 100644 index 0000000..e481409 --- /dev/null +++ b/services/subscription-service/src/models/invoice.model.ts @@ -0,0 +1,40 @@ +import {model, property} from '@loopback/repository'; +import {UserModifiableEntity} from '@sourceloop/core'; +import {InvoiceStatus} from '../types'; +@model({ + name: 'invoice', + description: 'invoice for a customer', +}) +export class Invoice extends UserModifiableEntity { + @property({ + type: 'string', + id: true, + generated: true, + }) + id?: string; + + @property({ + type: 'string', + name: 'invoice_id', + required: true, + }) + invoiceId: string; + + @property({ + type: 'string', + name: 'invoice_status', + description: 'payment or invoice status', + }) + invoiceStatus?: InvoiceStatus; + + @property({ + type: 'string', + name: 'billing_customer_id', + required: true, + }) + billingCustomerId: string; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/subscription-service/src/models/subscription.model.ts b/services/subscription-service/src/models/subscription.model.ts index 3b2f834..71c8381 100644 --- a/services/subscription-service/src/models/subscription.model.ts +++ b/services/subscription-service/src/models/subscription.model.ts @@ -3,6 +3,7 @@ import {SubscriptionStatus} from '../enums/subscription-status.enum'; import {numericEnumValues} from '../utils'; import {UserModifiableEntity} from '@sourceloop/core'; import {Plan} from './plan.model'; +import {Invoice} from './invoice.model'; @model({ name: 'subscriptions', @@ -53,6 +54,12 @@ export class Subscription extends UserModifiableEntity { }) planId: string; + @belongsTo(() => Invoice, undefined, { + description: 'invoice id of the subscription', + name: 'invoice_id', + }) + invoiceId: string; + constructor(data?: Partial) { super(data); } diff --git a/services/subscription-service/src/permissions.ts b/services/subscription-service/src/permissions.ts index d1b5c60..e167930 100644 --- a/services/subscription-service/src/permissions.ts +++ b/services/subscription-service/src/permissions.ts @@ -48,4 +48,16 @@ export const PermissionKey = { DeleteInvoice: '10214', ViewInvoice: '10215', CreateNotification: '2', + CreateBillingCustomer: '5321', + CreateBillingPaymentSource: '5322', + CreateBillingInvoice: '5323', + GetBillingCustomer: '5324', + GetBillingPaymentSource: '5325', + GetBillingInvoice: '5326', + UpdateBillingCustomer: '5327', + UpdateBillingPaymentSource: '5328', + UpdateBillingInvoice: '5329', + DeleteBillingCustomer: '5331', + DeleteBillingPaymentSource: '5332', + DeleteBillingInvoice: '5333', }; diff --git a/services/subscription-service/src/providers/index.ts b/services/subscription-service/src/providers/index.ts index e69de29..e1a63a7 100644 --- a/services/subscription-service/src/providers/index.ts +++ b/services/subscription-service/src/providers/index.ts @@ -0,0 +1 @@ +export * from './system-user.provider'; diff --git a/services/subscription-service/src/providers/system-user.provider.ts b/services/subscription-service/src/providers/system-user.provider.ts new file mode 100644 index 0000000..763b77c --- /dev/null +++ b/services/subscription-service/src/providers/system-user.provider.ts @@ -0,0 +1,13 @@ +import {Provider, ValueOrPromise} from '@loopback/core'; +import {IAuthUser} from 'loopback4-authorization'; +import {AnyObject} from '@loopback/repository'; +export class SystemUserProvider implements Provider { + value(): ValueOrPromise { + return { + username: 'SYSTEM', + id: process.env.SYSTEM_USER_ID!, + userTenantId: process.env.SYSTEM_USER_ID!, + permissions: [], + }; + } +} diff --git a/services/subscription-service/src/repositories/billing-customer.repository.ts b/services/subscription-service/src/repositories/billing-customer.repository.ts new file mode 100644 index 0000000..58deb2f --- /dev/null +++ b/services/subscription-service/src/repositories/billing-customer.repository.ts @@ -0,0 +1,42 @@ +import {Getter, inject} from '@loopback/core'; +import { + HasManyRepositoryFactory, + juggler, + repository, +} from '@loopback/repository'; +import { + DefaultUserModifyCrudRepository, + IAuthUserWithPermissions, +} from '@sourceloop/core'; +import {AuthenticationBindings} from 'loopback4-authentication'; +import {BillingCustomer, Invoice} from '../models'; +import {SubscriptionDbSourceName} from '../types'; +import {InvoiceRepository} from './invoice.repository'; + +export class BillingCustomerRepository extends DefaultUserModifyCrudRepository< + BillingCustomer, + typeof BillingCustomer.prototype.id, + {} +> { + public readonly invoices: HasManyRepositoryFactory< + Invoice, + typeof BillingCustomer.prototype.id + >; + + constructor( + @inject(`datasources.${SubscriptionDbSourceName}`) + dataSource: juggler.DataSource, + @repository.getter('InvoiceRepository') + protected invoiceRepositoryGetter: Getter, + + @inject.getter(AuthenticationBindings.CURRENT_USER) + public readonly getCurrentUser: Getter, + ) { + super(BillingCustomer, dataSource, getCurrentUser); + this.invoices = this.createHasManyRepositoryFactoryFor( + 'invoices', + invoiceRepositoryGetter, + ); + this.registerInclusionResolver('invoices', this.invoices.inclusionResolver); + } +} diff --git a/services/subscription-service/src/repositories/index.ts b/services/subscription-service/src/repositories/index.ts index 6a82b38..4379caa 100644 --- a/services/subscription-service/src/repositories/index.ts +++ b/services/subscription-service/src/repositories/index.ts @@ -5,3 +5,5 @@ export * from './subscription.repository'; export * from './billing-cycle.repository'; export * from './currency.repository'; export * from './plan-sizes.repository'; +export * from './billing-customer.repository'; +export * from './invoice.repository'; diff --git a/services/subscription-service/src/repositories/invoice.repository.ts b/services/subscription-service/src/repositories/invoice.repository.ts new file mode 100644 index 0000000..6cb5fbd --- /dev/null +++ b/services/subscription-service/src/repositories/invoice.repository.ts @@ -0,0 +1,24 @@ +import {Getter, inject} from '@loopback/core'; +import {juggler} from '@loopback/repository'; +import { + DefaultUserModifyCrudRepository, + IAuthUserWithPermissions, +} from '@sourceloop/core'; +import {AuthenticationBindings} from 'loopback4-authentication'; +import {Invoice} from '../models'; +import {SubscriptionDbSourceName} from '../types'; + +export class InvoiceRepository extends DefaultUserModifyCrudRepository< + Invoice, + typeof Invoice.prototype.id, + {} +> { + constructor( + @inject(`datasources.${SubscriptionDbSourceName}`) + dataSource: juggler.DataSource, + @inject.getter(AuthenticationBindings.CURRENT_USER) + public readonly getCurrentUser: Getter, + ) { + super(Invoice, dataSource, getCurrentUser); + } +} diff --git a/services/subscription-service/src/repositories/subscription.repository.ts b/services/subscription-service/src/repositories/subscription.repository.ts index 8c57007..d496b0d 100644 --- a/services/subscription-service/src/repositories/subscription.repository.ts +++ b/services/subscription-service/src/repositories/subscription.repository.ts @@ -1,6 +1,6 @@ import {inject, Getter} from '@loopback/core'; import {repository, BelongsToAccessor, juggler} from '@loopback/repository'; -import {Subscription, SubscriptionRelations, Plan} from '../models'; +import {Subscription, SubscriptionRelations, Plan, Invoice} from '../models'; import {PlanRepository} from './plan.repository'; import {AuthenticationBindings} from 'loopback4-authentication'; import { @@ -8,6 +8,7 @@ import { IAuthUserWithPermissions, } from '@sourceloop/core'; import {SubscriptionDbSourceName} from '../types'; +import {InvoiceRepository} from './invoice.repository'; export class SubscriptionRepository extends DefaultUserModifyCrudRepository< Subscription, @@ -19,6 +20,11 @@ export class SubscriptionRepository extends DefaultUserModifyCrudRepository< typeof Subscription.prototype.id >; + public readonly invoice: BelongsToAccessor< + Invoice, + typeof Subscription.prototype.id + >; + constructor( @inject(`datasources.${SubscriptionDbSourceName}`) dataSource: juggler.DataSource, @@ -26,8 +32,15 @@ export class SubscriptionRepository extends DefaultUserModifyCrudRepository< public readonly getCurrentUser: Getter, @repository.getter('PlanRepository') protected planRepositoryGetter: Getter, + @repository.getter('InvoiceRepository') + protected invoiceRepositoryGetter: Getter, ) { super(Subscription, dataSource, getCurrentUser); + this.invoice = this.createBelongsToAccessorFor( + 'invoice', + invoiceRepositoryGetter, + ); + this.registerInclusionResolver('invoice', this.invoice.inclusionResolver); this.plan = this.createBelongsToAccessorFor('plan', planRepositoryGetter); this.registerInclusionResolver('plan', this.plan.inclusionResolver); } diff --git a/services/subscription-service/src/types.ts b/services/subscription-service/src/types.ts index 7f53cd8..193cef1 100644 --- a/services/subscription-service/src/types.ts +++ b/services/subscription-service/src/types.ts @@ -2,6 +2,7 @@ // // This software is released under the MIT License. // https://opensource.org/licenses/MIT +import {IInvoice} from 'loopback4-billing'; import {IServiceConfig} from '@sourceloop/core'; // sonarignore:start @@ -24,3 +25,24 @@ export type LeadUserWithToken = { export const SubscriptionDbSourceName = 'SubscriptionDB'; // sonarignore:end + +export type InvoiceStatus = + | 'paid' + | 'posted' + | 'payment_due' + | 'not_paid' + | 'voided' + | 'pending'; + +export interface IPayload { + id: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + event_type: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + webhook_status: string; + content: IContent; +} + +export interface IContent { + invoice: IInvoice; +} diff --git a/services/tenant-management-service/migrations/pg/migrations/20240925102459-add-table-tenant-configs.js b/services/tenant-management-service/migrations/pg/migrations/20240925102459-add-table-tenant-configs.js new file mode 100644 index 0000000..9e7d6e8 --- /dev/null +++ b/services/tenant-management-service/migrations/pg/migrations/20240925102459-add-table-tenant-configs.js @@ -0,0 +1,59 @@ +'use strict'; + +var dbm; +var type; +var seed; +var fs = require('fs'); +var path = require('path'); +var Promise; + +/** + * We receive the dbmigrate dependency from dbmigrate initially. + * This enables us to not have to rely on NODE_PATH. + */ +exports.setup = function (options, seedLink) { + dbm = options.dbmigrate; + type = dbm.dataType; + seed = seedLink; + Promise = options.Promise; +}; + +exports.up = function (db) { + var filePath = path.join( + __dirname, + 'sqls', + '20240925102459-add-table-tenant-configs-up.sql', + ); + return new Promise(function (resolve, reject) { + fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) { + if (err) return reject(err); + console.log('received data: ' + data); + + resolve(data); + }); + }).then(function (data) { + return db.runSql(data); + }); +}; + +exports.down = function (db) { + var filePath = path.join( + __dirname, + 'sqls', + '20240925102459-add-table-tenant-configs-down.sql', + ); + return new Promise(function (resolve, reject) { + fs.readFile(filePath, {encoding: 'utf-8'}, function (err, data) { + if (err) return reject(err); + console.log('received data: ' + data); + + resolve(data); + }); + }).then(function (data) { + return db.runSql(data); + }); +}; + +exports._meta = { + version: 1, +}; diff --git a/services/tenant-management-service/migrations/pg/migrations/sqls/20240925102459-add-table-tenant-configs-down.sql b/services/tenant-management-service/migrations/pg/migrations/sqls/20240925102459-add-table-tenant-configs-down.sql new file mode 100644 index 0000000..3284c15 --- /dev/null +++ b/services/tenant-management-service/migrations/pg/migrations/sqls/20240925102459-add-table-tenant-configs-down.sql @@ -0,0 +1 @@ +drop table main.tenant_configs; \ No newline at end of file diff --git a/services/tenant-management-service/migrations/pg/migrations/sqls/20240925102459-add-table-tenant-configs-up.sql b/services/tenant-management-service/migrations/pg/migrations/sqls/20240925102459-add-table-tenant-configs-up.sql new file mode 100644 index 0000000..9681a4c --- /dev/null +++ b/services/tenant-management-service/migrations/pg/migrations/sqls/20240925102459-add-table-tenant-configs-up.sql @@ -0,0 +1,33 @@ +CREATE TABLE IF NOT EXISTS main.tenant_configs +( + id uuid NOT NULL DEFAULT (md5(((random())::text || (clock_timestamp())::text)))::uuid, + config_key varchar(100) NOT NULL, + config_value jsonb NOT NULL, + created_on timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + modified_on timestamptz DEFAULT CURRENT_TIMESTAMP NOT NULL, + created_by uuid, + modified_by uuid, + deleted boolean DEFAULT FALSE NOT NULL, + deleted_by uuid, + deleted_on timestamptz, + tenant_id uuid NOT NULL, + CONSTRAINT pk_tenant_configs_id PRIMARY KEY (id), + CONSTRAINT fk_tenant_configs_tenants FOREIGN KEY (tenant_id) + REFERENCES main.tenants(id) +); + + +CREATE OR REPLACE FUNCTION main.moddatetime() + RETURNS TRIGGER + LANGUAGE plpgsql + AS $function$ +BEGIN + NEW.modified_on = now(); + RETURN NEW; +END; +$function$; + +CREATE TRIGGER mdt_tenant_configs + BEFORE UPDATE ON main.tenant_configs + FOR EACH ROW + EXECUTE FUNCTION main.moddatetime('modified_on'); diff --git a/services/tenant-management-service/package.json b/services/tenant-management-service/package.json index 62c7912..335b615 100644 --- a/services/tenant-management-service/package.json +++ b/services/tenant-management-service/package.json @@ -101,10 +101,12 @@ "@loopback/build": "^11.0.2", "@loopback/eslint-config": "^15.0.2", "@loopback/testlab": "^7.0.2", + "@types/auth0": "^3.3.10", "@types/jsonwebtoken": "^9.0.5", "@types/moment": "^2.13.0", "@types/node": "^18.11.9", "@types/pdfkit": "^0.13.4", + "auth0": "^4.10.0", "eslint": "^8.57.0", "nodemon": "^2.0.21", "nyc": "^15.1.0", diff --git a/services/tenant-management-service/src/component.ts b/services/tenant-management-service/src/component.ts index 33fc487..17e53cd 100644 --- a/services/tenant-management-service/src/component.ts +++ b/services/tenant-management-service/src/component.ts @@ -14,8 +14,8 @@ import { ProviderMap, ServiceOrProviderClass, } from '@loopback/core'; -import {Class, Model, Repository} from '@loopback/repository'; -import {RestApplication} from '@loopback/rest'; +import { Class, Model, Repository } from '@loopback/repository'; +import { RestApplication } from '@loopback/rest'; import { BearerVerifierBindings, BearerVerifierComponent, @@ -25,11 +25,20 @@ import { SECURITY_SCHEME_SPEC, ServiceSequence, } from '@sourceloop/core'; -import {AuthenticationComponent} from 'loopback4-authentication'; +import { AuthenticationComponent } from 'loopback4-authentication'; import { AuthorizationBindings, AuthorizationComponent, } from 'loopback4-authorization'; +import { + ContactController, + HomePageController, + LeadController, + LeadTenantController, + PingController, + TenantController, +} from './controllers'; +import { InvoiceController } from './controllers/invoice.controller'; import { EventConnectorBinding, LEAD_TOKEN_VERIFIER, @@ -45,33 +54,37 @@ import { LeadController, PingController, TenantController, + TenantConfigController, + TenantConfigTenantController, } from './controllers'; import { Address, Contact, + CreateLeadDTO, Invoice, Lead, LeadToken, + ProvisioningDTO, Resource, Tenant, - WebhookSecret, - CreateLeadDTO, - ProvisioningDTO, + TenantConfig, TenantOnboardDTO, VerifyLeadResponseDTO, WebhookDTO, + WebhookSecret, } from './models'; +import { LeadTokenVerifierProvider, SystemUserProvider } from './providers'; import { AddressRepository, ContactRepository, InvoiceRepository, - LeadTokenRepository, LeadRepository, + LeadTokenRepository, ResourceRepository, + TenantConfigRepository, TenantRepository, WebhookSecretRepository, } from './repositories'; -import {LeadTokenVerifierProvider, SystemUserProvider} from './providers'; import { CryptoHelperService, EventConnector, @@ -81,6 +94,8 @@ import { OnboardingService, ProvisioningService, } from './services'; +import {IdpController} from './controllers/idp.controller'; + export class TenantManagementServiceComponent implements Component { constructor( @inject(CoreBindings.APPLICATION_INSTANCE) @@ -120,6 +135,7 @@ export class TenantManagementServiceComponent implements Component { ResourceRepository, TenantRepository, WebhookSecretRepository, + TenantConfigRepository, ]; this.models = [ @@ -136,6 +152,7 @@ export class TenantManagementServiceComponent implements Component { TenantOnboardDTO, VerifyLeadResponseDTO, WebhookDTO, + TenantConfig, ]; this.controllers = [ @@ -146,11 +163,15 @@ export class TenantManagementServiceComponent implements Component { LeadController, PingController, TenantController, + IdpController, + TenantConfigController, + TenantConfigTenantController, ]; this.bindings = [ Binding.bind(LEAD_TOKEN_VERIFIER).toProvider(LeadTokenVerifierProvider), Binding.bind(SYSTEM_USER).toProvider(SystemUserProvider), + createServiceBinding(ProvisioningService), createServiceBinding(OnboardingService), createServiceBinding(LeadAuthenticator), diff --git a/services/tenant-management-service/src/controllers/idp.controller.ts b/services/tenant-management-service/src/controllers/idp.controller.ts new file mode 100644 index 0000000..15ee0e5 --- /dev/null +++ b/services/tenant-management-service/src/controllers/idp.controller.ts @@ -0,0 +1,65 @@ +import {inject, intercept} from '@loopback/core'; +import {getModelSchemaRef, post, requestBody} from '@loopback/rest'; +import { + CONTENT_TYPE, + OPERATION_SECURITY_SPEC, + rateLimitKeyGenPublic, + STATUS_CODE, +} from '@sourceloop/core'; +import {authorize} from 'loopback4-authorization'; +import {ratelimit} from 'loopback4-ratelimiter'; +import {TenantManagementServiceBindings, WEBHOOK_VERIFIER} from '../keys'; +import {IdpDetailsDTO} from '../models/dtos/idp-details-dto.model'; +import {ConfigureIdpFunc, IdPKey} from '../types'; + +const basePath = '/manage/users'; +export class IdpController { + constructor( + @inject(TenantManagementServiceBindings.IDP_KEYCLOAK) + private readonly idpKeycloakProvider: ConfigureIdpFunc, + @inject(TenantManagementServiceBindings.IDP_AUTH0) + private readonly idpAuth0Provider: ConfigureIdpFunc, + ) {} + @intercept(WEBHOOK_VERIFIER) + @ratelimit(true, { + max: parseInt(process.env.WEBHOOK_API_MAX_ATTEMPTS ?? '10'), + keyGenerator: rateLimitKeyGenPublic, + }) + @authorize({ + permissions: ['*'], + }) + @post(`${basePath}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Webhook success', + }, + }, + }) + async idpConfigure( + @requestBody({ + content: { + [CONTENT_TYPE.JSON]: { + schema: getModelSchemaRef(IdpDetailsDTO, { + title: 'IdpDetailsDTO', + }), + }, + }, + }) + payload: IdpDetailsDTO, + ): Promise { + switch (payload.identityProvider) { + case IdPKey.AUTH0: + await this.idpAuth0Provider(payload); + break; + case IdPKey.COGNITO: + break; + case IdPKey.KEYCLOAK: + await this.idpKeycloakProvider(payload); + break; + + default: + break; + } + } +} diff --git a/services/tenant-management-service/src/controllers/index.ts b/services/tenant-management-service/src/controllers/index.ts index 8406aab..f0b8847 100644 --- a/services/tenant-management-service/src/controllers/index.ts +++ b/services/tenant-management-service/src/controllers/index.ts @@ -6,3 +6,6 @@ export * from './lead-tenant.controller'; export * from './tenant.controller'; export * from './webhook.controller'; export * from './invoice.controller'; +export * from './tenant-config.controller'; +export * from './tenant-config-tenant.controller'; +export * from './idp.controller'; \ No newline at end of file diff --git a/services/tenant-management-service/src/controllers/tenant-config-tenant.controller.ts b/services/tenant-management-service/src/controllers/tenant-config-tenant.controller.ts new file mode 100644 index 0000000..5a3e99e --- /dev/null +++ b/services/tenant-management-service/src/controllers/tenant-config-tenant.controller.ts @@ -0,0 +1,39 @@ +import {repository} from '@loopback/repository'; +import {param, get, getModelSchemaRef} from '@loopback/rest'; +import {TenantConfig, Tenant} from '../models'; +import {TenantConfigRepository} from '../repositories'; +import {authenticate, STRATEGY} from 'loopback4-authentication'; +import {authorize} from 'loopback4-authorization'; +import {PermissionKey} from '../permissions'; +import {OPERATION_SECURITY_SPEC, STATUS_CODE} from '@sourceloop/core'; +const basePath = '/tenant-configs/{id}/tenant'; +export class TenantConfigTenantController { + constructor( + @repository(TenantConfigRepository) + public tenantConfigRepository: TenantConfigRepository, + ) {} + @authorize({ + permissions: [PermissionKey.ViewTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(`${basePath}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'Tenant belonging to TenantConfig', + content: { + 'application/json': { + schema: getModelSchemaRef(Tenant), + }, + }, + }, + }, + }) + async getTenant( + @param.path.string('id') id: typeof TenantConfig.prototype.id, + ): Promise { + return this.tenantConfigRepository.tenant(id); + } +} diff --git a/services/tenant-management-service/src/controllers/tenant-config.controller.ts b/services/tenant-management-service/src/controllers/tenant-config.controller.ts new file mode 100644 index 0000000..fc690cc --- /dev/null +++ b/services/tenant-management-service/src/controllers/tenant-config.controller.ts @@ -0,0 +1,239 @@ +import { + Count, + CountSchema, + Filter, + FilterExcludingWhere, + repository, + Where, +} from '@loopback/repository'; +import { + post, + param, + get, + getModelSchemaRef, + patch, + put, + del, + requestBody, +} from '@loopback/rest'; +import {TenantConfig} from '../models'; +import {TenantConfigRepository} from '../repositories'; +import {authenticate, STRATEGY} from 'loopback4-authentication'; +import {authorize} from 'loopback4-authorization'; +import {PermissionKey} from '../permissions'; +import { + CONTENT_TYPE, + OPERATION_SECURITY_SPEC, + STATUS_CODE, +} from '@sourceloop/core'; +const basePath = '/tenant-configs'; +export class TenantConfigController { + constructor( + @repository(TenantConfigRepository) + public tenantConfigRepository: TenantConfigRepository, + ) {} + @authorize({ + permissions: [PermissionKey.CreateTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @post(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'Tenant Config model instance', + content: { + [CONTENT_TYPE.JSON]: {schema: getModelSchemaRef(TenantConfig)}, + }, + }, + }, + }) + async create( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(TenantConfig, { + title: 'NewTenantConfig', + exclude: ['id'], + }), + }, + }, + }) + tenantConfig: Omit, + ): Promise { + return this.tenantConfigRepository.create(tenantConfig); + } + @authorize({ + permissions: [PermissionKey.ViewTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(`${basePath}/count`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'Tenant Config model count', + content: {[CONTENT_TYPE.JSON]: {schema: CountSchema}}, + }, + }, + }) + async count( + @param.where(TenantConfig) where?: Where, + ): Promise { + return this.tenantConfigRepository.count(where); + } + @authorize({ + permissions: [PermissionKey.ViewTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'Array of TenantConfig model instances', + content: { + [CONTENT_TYPE.JSON]: { + schema: { + type: 'array', + items: getModelSchemaRef(TenantConfig, {includeRelations: true}), + }, + }, + }, + }, + }, + }) + async find( + @param.filter(TenantConfig) filter?: Filter, + ): Promise { + return this.tenantConfigRepository.find(filter); + } + @authorize({ + permissions: [PermissionKey.UpdateTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @patch(basePath, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'Tenant Config PATCH success', + content: { + [CONTENT_TYPE.JSON]: { + schema: getModelSchemaRef(TenantConfig), + }, + }, + }, + }, + }) + async updateAll( + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(TenantConfig, {partial: true}), + }, + }, + }) + tenantConfig: TenantConfig, + @param.where(TenantConfig) where?: Where, + ): Promise { + return this.tenantConfigRepository.updateAll(tenantConfig, where); + } + @authorize({ + permissions: [PermissionKey.ViewTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @get(`${basePath}/{id}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.OK]: { + description: 'Tenant Config model instance', + content: { + [CONTENT_TYPE.JSON]: {schema: getModelSchemaRef(TenantConfig)}, + }, + }, + }, + }) + async findById( + @param.path.string('id') id: string, + @param.filter(TenantConfig, {exclude: 'where'}) + filter?: FilterExcludingWhere, + ): Promise { + return this.tenantConfigRepository.findById(id, filter); + } + @authorize({ + permissions: [PermissionKey.UpdateTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @patch(`${basePath}/{id}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Tenant Config PATCH success', + content: { + [CONTENT_TYPE.JSON]: { + schema: getModelSchemaRef(TenantConfig), + }, + }, + }, + }, + }) + async updateById( + @param.path.string('id') id: string, + @requestBody({ + content: { + 'application/json': { + schema: getModelSchemaRef(TenantConfig, {partial: true}), + }, + }, + }) + tenantConfig: TenantConfig, + ): Promise { + await this.tenantConfigRepository.updateById(id, tenantConfig); + } + @authorize({ + permissions: [PermissionKey.UpdateTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @put(`${basePath}/{id}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Tenant Config PUT success', + }, + }, + }) + async replaceById( + @param.path.string('id') id: string, + @requestBody() tenantConfig: TenantConfig, + ): Promise { + await this.tenantConfigRepository.replaceById(id, tenantConfig); + } + @authorize({ + permissions: [PermissionKey.DeleteTenantConfig], + }) + @authenticate(STRATEGY.BEARER, { + passReqToCallback: true, + }) + @del(`${basePath}/{id}`, { + security: OPERATION_SECURITY_SPEC, + responses: { + [STATUS_CODE.NO_CONTENT]: { + description: 'Tenant DELETE success', + }, + }, + }) + async deleteById(@param.path.string('id') id: string): Promise { + await this.tenantConfigRepository.deleteById(id); + } +} diff --git a/services/tenant-management-service/src/keys.ts b/services/tenant-management-service/src/keys.ts index d9edad1..a2f676a 100644 --- a/services/tenant-management-service/src/keys.ts +++ b/services/tenant-management-service/src/keys.ts @@ -1,5 +1,6 @@ import {VerifyFunction} from 'loopback4-authentication'; import { + ConfigureIdpFunc, ITenantManagementServiceConfig, LeadUser, WebhookConfig, @@ -16,12 +17,24 @@ import { } from '@loopback/core'; import {BINDING_PREFIX} from '@sourceloop/core'; import {IEventConnector} from './types/i-event-connector.interface'; +import {Auth0Response} from './providers/idp'; export namespace TenantManagementServiceBindings { export const Config = BindingKey.create( `${BINDING_PREFIX}.chat.config`, ); + /** + * Binding key for the Idp keycloak provider. + */ + export const IDP_KEYCLOAK = BindingKey.create>( + 'sf.user.idp.keycloak', + ); + /** + * Binding key for the Idp Auth0 provider. + */ + export const IDP_AUTH0 = + BindingKey.create>('sf.user.idp.auth0'); } /** diff --git a/services/tenant-management-service/src/models/dtos/idp-details-dto.model.ts b/services/tenant-management-service/src/models/dtos/idp-details-dto.model.ts new file mode 100644 index 0000000..6f606d1 --- /dev/null +++ b/services/tenant-management-service/src/models/dtos/idp-details-dto.model.ts @@ -0,0 +1,31 @@ +import {getJsonSchema} from '@loopback/openapi-v3'; +import {Model, model, property} from '@loopback/repository'; +import {IdpDetails, IdPKey} from '../../types'; +import {TenantDto} from './tenant-dto.model'; + +@model({ + description: 'model describing payload for IDP controller', +}) +export class IdpDetailsDTO extends Model implements IdpDetails { + @property({ + type: 'string', + description: 'identity provider - auth0 , keycloak , cognito', + required: true, + default: IdPKey.AUTH0, + jsonSchema: { + enum: Object.values(IdPKey), + }, + }) + identityProvider: IdPKey; + + @property({ + type: 'object', + description: 'address object to be created for the lead', + jsonSchema: getJsonSchema(TenantDto), + }) + tenant: TenantDto; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/tenant-management-service/src/models/dtos/subscription-dto.model.ts b/services/tenant-management-service/src/models/dtos/subscription-dto.model.ts index a3fbb84..f30a44b 100644 --- a/services/tenant-management-service/src/models/dtos/subscription-dto.model.ts +++ b/services/tenant-management-service/src/models/dtos/subscription-dto.model.ts @@ -42,6 +42,9 @@ export class SubscriptionDTO implements ISubscription { @property({type: 'string'}) planId: string; + @property({type: 'string'}) + invoiceId: string; + // Assuming IPlan interface is defined, you can include it here @property() plan?: IPlan; diff --git a/services/tenant-management-service/src/models/dtos/tenant-dto.model.ts b/services/tenant-management-service/src/models/dtos/tenant-dto.model.ts new file mode 100644 index 0000000..c49b673 --- /dev/null +++ b/services/tenant-management-service/src/models/dtos/tenant-dto.model.ts @@ -0,0 +1,39 @@ +import {getJsonSchema} from '@loopback/openapi-v3'; +import {AnyObject, model, property} from '@loopback/repository'; +import {Address} from '../address.model'; + +import {Tenant} from '../tenant.model'; +import {Contact} from '../contact.model'; + +@model({ + description: 'model describing payload used to create a lead', +}) +export class TenantDto extends Tenant { + @property({ + type: 'object', + description: 'address object to be created for the lead', + jsonSchema: getJsonSchema(Address), + }) + address: Address; + + @property({ + type: 'array', + itemType: 'object', + description: 'Array of contact objects', + jsonSchema: { + type: 'object', + items: getJsonSchema(Contact), + }, + }) + contacts: Contact[]; + @property({ + type: 'object', + description: 'plan details', + jsonSchema: getJsonSchema(Object), + }) + plan: AnyObject; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/tenant-management-service/src/models/index.ts b/services/tenant-management-service/src/models/index.ts index 02b5768..d463d4d 100644 --- a/services/tenant-management-service/src/models/index.ts +++ b/services/tenant-management-service/src/models/index.ts @@ -7,3 +7,4 @@ export * from './resource.model'; export * from './invoice.model'; export * from './address.model'; export * from './lead-token.model'; +export * from './tenant-config.model'; diff --git a/services/tenant-management-service/src/models/tenant-config.model.ts b/services/tenant-management-service/src/models/tenant-config.model.ts new file mode 100644 index 0000000..17c5945 --- /dev/null +++ b/services/tenant-management-service/src/models/tenant-config.model.ts @@ -0,0 +1,47 @@ +import {model, property, belongsTo} from '@loopback/repository'; +import {UserModifiableEntity} from '@sourceloop/core'; +import {Tenant} from './tenant.model'; +import {ConfigValue} from '../providers/idp'; + +@model({ + name: 'tenant_configs', + description: 'tenant_configs to save any tenant specific data related to idP', +}) +export class TenantConfig extends UserModifiableEntity { + @property({ + type: 'string', + id: true, + generated: true, + }) + id: string; + + @property({ + type: 'string', + required: true, + name: 'config_key', + }) + configKey: string; + + @property({ + type: 'object', + required: true, + name: 'config_value', + }) + configValue: ConfigValue; + + @belongsTo( + () => Tenant, + {keyTo: 'id'}, + { + type: 'string', + name: 'tenant_id', + description: 'id of the tenant this invoice is generated for', + required: true, + }, + ) + tenantId: string; + + constructor(data?: Partial) { + super(data); + } +} diff --git a/services/tenant-management-service/src/permissions.ts b/services/tenant-management-service/src/permissions.ts index 9da2602..c4953c2 100644 --- a/services/tenant-management-service/src/permissions.ts +++ b/services/tenant-management-service/src/permissions.ts @@ -17,6 +17,11 @@ export const PermissionKey = { DeleteInvoice: '10214', ViewInvoice: '10215', CreateNotification: '2', + CreateTenantConfig: '10216', + + UpdateTenantConfig: '10217', + DeleteTenantConfig: '10218', + ViewTenantConfig: '10219', CreateSubscription: '7001', UpdateSubscription: '7002', @@ -28,4 +33,17 @@ export const PermissionKey = { CreateNotificationTemplate: '8001', UpdateNotificationTemplate: '8002', DeleteNotificationTemplate: '8003', + + CreateBillingCustomer: '5321', + CreateBillingPaymentSource: '5322', + CreateBillingInvoice: '5323', + GetBillingCustomer: '5324', + GetBillingPaymentSource: '5325', + GetBillingInvoice: '5326', + UpdateBillingCustomer: '5327', + UpdateBillingPaymentSource: '5328', + UpdateBillingInvoice: '5329', + DeleteBillingCustomer: '5331', + DeleteBillingPaymentSource: '5332', + DeleteBillingInvoice: '5333', }; diff --git a/services/tenant-management-service/src/providers/idp/idp-auth0.provider.ts b/services/tenant-management-service/src/providers/idp/idp-auth0.provider.ts new file mode 100644 index 0000000..b9219c1 --- /dev/null +++ b/services/tenant-management-service/src/providers/idp/idp-auth0.provider.ts @@ -0,0 +1,164 @@ +import {Provider} from '@loopback/context'; + +import {ConfigureIdpFunc, IdpDetails} from '../../types'; +import {ManagementClient} from 'auth0'; + +import {Auth0Response, ConfigValue, OrganizationData, UserData} from './types'; + +import {TenantConfigRepository} from '../../repositories/tenant-config.repository'; +import {repository} from '@loopback/repository'; + +import {HttpErrors} from '@loopback/rest'; +const STATUS_OK = 200; +const STATUS_NOT_FOUND = 404; +export class Auth0IdpProvider + implements Provider> +{ + management: ManagementClient; + + constructor( + @repository(TenantConfigRepository) + private readonly tenantConfigRepository: TenantConfigRepository, + ) {} + + value(): ConfigureIdpFunc { + return payload => this.configure(payload); + } + async configure(payload: IdpDetails): Promise { + this.management = new ManagementClient({ + domain: process.env.AUTH0_DOMAIN ?? '', + clientId: process.env.AUTH0_CLIENT_ID ?? '', + clientSecret: process.env.AUTH0_CLIENT_SECRET ?? '', + audience: process.env.AUTH0_AUDIENCE, + }); + const {tenant} = payload; + const planTier = tenant.plan.tier; + const tenantConfig = await this.tenantConfigRepository.findOne({ + where: {tenantId: tenant.id}, + }); + if (!tenantConfig) { + throw new HttpErrors.NotFound( + `Tenant configuration not found for tenant: ${tenant.id}`, + ); + } + const configValue: ConfigValue = tenantConfig.configValue; + const organizationData: OrganizationData = { + name: tenant.name, + // eslint-disable-next-line + display_name: configValue.display_name, + // eslint-disable-next-line + logo_url: configValue.logo_url, + // eslint-disable-next-line + primary_color: configValue.primary_color, + // eslint-disable-next-line + page_background: configValue.page_background, + // eslint-disable-next-line + link_color: configValue.link_color, + }; + + const userData: UserData = { + email: tenant.contacts[0].email, + + connection: configValue.connection, + password: configValue.password, + // eslint-disable-next-line + verify_email: configValue.verify_email, + // eslint-disable-next-line + phone_number: configValue.phone_number, + // eslint-disable-next-line + user_metadata: configValue.user_metadata, + blocked: configValue.blocked, + // eslint-disable-next-line + email_verified: configValue.email_verified, + // eslint-disable-next-line + app_metadata: configValue.app_metadata, + // eslint-disable-next-line + given_name: configValue.given_name, + // eslint-disable-next-line + family_name: configValue.family_name, + nickname: configValue.nickname, + picture: configValue.picture, + // eslint-disable-next-line + user_id: configValue.user_id, + }; + + let organizationId!: string; + + if (planTier === 'PREMIUM') { + const organization = await this.createOrganization(organizationData); + organizationId = organization.data.id; + } else { + try { + const organizationResponse = + await this.management.organizations.getByName({name: tenant.name}); + + if (organizationResponse.status === STATUS_OK) { + organizationId = organizationResponse.data.id; + } + } catch (error) { + if (error.statusCode === STATUS_NOT_FOUND) { + const organization = await this.createOrganization(organizationData); + organizationId = organization.data.id; + } else { + throw new Error(`Error checking organization: ${error.message}`); + } + } + } + + if (!organizationId) { + throw new Error('Failed to retrieve or create organization ID.'); + } + + const user = await this.createUser(userData); + const userId = user.data.user_id; + + await this.addMemberToOrganization(organizationId, userId); + return { + organizationId: organizationId, + userId: userId, + }; + } + async createOrganization(data: OrganizationData) { + try { + return await this.management.organizations.create({ + name: data.name, + // eslint-disable-next-line + display_name: data.display_name, + branding: { + // eslint-disable-next-line + logo_url: data.logo_url, + colors: { + primary: data.primary_color ?? '#007BFF', + // eslint-disable-next-line + page_background: data.page_background ?? '#007BFF', + }, + }, + // eslint-disable-next-line + enabled_connections: [], + }); + } catch (error) { + throw new Error(`Error creating organization: ${error.message}`); + } + } + async createUser(userData: UserData) { + try { + return await this.management.users.create(userData); + } catch (error) { + throw new Error(`Error creating user: ${error.message}`); + } + } + + async addMemberToOrganization(organizationId: string, userId: string) { + try { + return await this.management.organizations.addMembers( + {id: organizationId}, + { + members: [userId], + }, + ); + } catch (error) { + throw new Error(`Error adding member to organization: ${error.message}`); + } + } + +} diff --git a/services/tenant-management-service/src/providers/idp/idp-keycloak.provider.ts b/services/tenant-management-service/src/providers/idp/idp-keycloak.provider.ts new file mode 100644 index 0000000..d583006 --- /dev/null +++ b/services/tenant-management-service/src/providers/idp/idp-keycloak.provider.ts @@ -0,0 +1,136 @@ +import {Provider} from '@loopback/context'; +import axios from 'axios'; +import qs from 'qs'; +import {ConfigureIdpFunc, IdpDetails} from '../../types'; + +interface TokenResponse { + // eslint-disable-next-line + access_token: string; +} + +interface Credentials { + type: string; + value: string; + temporary: boolean; +} + +export class KeycloakIdpProvider implements Provider> { + constructor() {} + + value(): ConfigureIdpFunc { + return payload => this.configure(payload); + } + async configure(payload: IdpDetails): Promise { + const {tenant} = payload; + + try { + const token = await this.authenticateAdmin(); + // 1. Create a new realm using the tenant key + await this.createRealm(tenant.key, token); + + // 2. Create a new client within the realm + const clientId = `client-${tenant.key}`; // You can customize this as needed + await this.createClient(tenant.key, clientId, token); + + // 3. Create a new admin user for the tenant + const adminUsername = `${tenant.key}-admin`; // Customize this as needed + const adminPassword = 'your-secure-password'; // This can be dynamic or set in the environment + await this.createUser(tenant.key, adminUsername, adminPassword, token); + } catch (error) { + throw new Error( + `Failed to configure Keycloak for tenant: ${tenant.name}`, + ); + } + } + + async authenticateAdmin(): Promise { + const response = await axios.post( + `${process.env.KEYCLOAK_HOST}/realms/master/protocol/openid-connect/token`, + qs.stringify({ + username: process.env.KEYCLOAK_ADMIN_USERNAME, + password: process.env.KEYCLOAK_ADMIN_PASSWORD, + // eslint-disable-next-line + grant_type: 'password', + // eslint-disable-next-line + client_id: 'admin-cli', + }), + { + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + }, + ); + + return response.data.access_token; + } + + async createRealm(realmName: string, token: string): Promise { + // const token = await this.authenticateAdmin(); + + await axios.post( + `${process.env.KEYCLOAK_HOST}/admin/realms`, + { + realm: realmName, + enabled: true, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + } + + async createClient( + realmName: string, + clientId: string, + token: string, + ): Promise { + // const token = await this.authenticateAdmin(); + + await axios.post( + `${process.env.KEYCLOAK_HOST}/admin/realms/${realmName}/clients`, + { + clientId: clientId, + publicClient: true, + directAccessGrantsEnabled: true, + protocol: 'openid-connect', + enabled: true, + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + } + + async createUser( + realmName: string, + username: string, + password: string, + token: string, + ): Promise { + // const token = await this.authenticateAdmin(); + + await axios.post( + `${process.env.KEYCLOAK_HOST}/admin/realms/${realmName}/users`, + { + username: username, + enabled: true, + credentials: [ + { + type: 'password', + value: password, + temporary: false, + }, + ] as Credentials[], + }, + { + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + } +} diff --git a/services/tenant-management-service/src/providers/idp/index.ts b/services/tenant-management-service/src/providers/idp/index.ts new file mode 100644 index 0000000..0f157fc --- /dev/null +++ b/services/tenant-management-service/src/providers/idp/index.ts @@ -0,0 +1,3 @@ +export * from './idp-auth0.provider'; +export * from './idp-keycloak.provider'; +export * from './types'; diff --git a/services/tenant-management-service/src/providers/idp/types.ts b/services/tenant-management-service/src/providers/idp/types.ts new file mode 100644 index 0000000..d3b7c91 --- /dev/null +++ b/services/tenant-management-service/src/providers/idp/types.ts @@ -0,0 +1,47 @@ +import {PostInvitationsRequestAppMetadata} from 'auth0'; +export interface UserData { + email?: string; + // eslint-disable-next-line + phone_number?: string; + // eslint-disable-next-line + user_metadata?: {[key: string]: any}; //NOSONAR + blocked?: boolean; + // eslint-disable-next-line + email_verified?: boolean; + // eslint-disable-next-line + app_metadata?: PostInvitationsRequestAppMetadata; + // eslint-disable-next-line + given_name?: string; + // eslint-disable-next-line + family_name?: string; + name?: string; + nickname?: string; + picture?: string; + // eslint-disable-next-line + user_id?: string; + connection: string; + password?: string; + // eslint-disable-next-line + verify_email?: boolean; + username?: string; +} +export interface OrganizationData { + name: string; + // eslint-disable-next-line + display_name?: string; + // eslint-disable-next-line + logo_url?: string; + // eslint-disable-next-line + primary_color?: string; + // eslint-disable-next-line + page_background?: string; + // eslint-disable-next-line + link_color?: string; +} +export interface ConfigValue extends Omit, OrganizationData { + username: UserData['name']; +} +export type Auth0Response = { + organizationId: string; + userId: string; +}; diff --git a/services/tenant-management-service/src/providers/index.ts b/services/tenant-management-service/src/providers/index.ts index ec52e1f..d12ce03 100644 --- a/services/tenant-management-service/src/providers/index.ts +++ b/services/tenant-management-service/src/providers/index.ts @@ -1,2 +1,3 @@ export * from './lead-token-verify.provider'; export * from './system-user.provider'; +export * from './idp' \ No newline at end of file diff --git a/services/tenant-management-service/src/repositories/index.ts b/services/tenant-management-service/src/repositories/index.ts index 5c5157a..7f7c28c 100644 --- a/services/tenant-management-service/src/repositories/index.ts +++ b/services/tenant-management-service/src/repositories/index.ts @@ -6,3 +6,4 @@ export * from './resource.repository'; export * from './invoice.repository'; export * from './address.repository'; export * from './lead-token.repository'; +export * from './tenant-config.repository'; diff --git a/services/tenant-management-service/src/repositories/saas-tenant.repository.ts b/services/tenant-management-service/src/repositories/saas-tenant.repository.ts new file mode 100644 index 0000000..72b8ae5 --- /dev/null +++ b/services/tenant-management-service/src/repositories/saas-tenant.repository.ts @@ -0,0 +1,89 @@ +import {Getter, inject} from '@loopback/core'; +import { + BelongsToAccessor, + HasManyRepositoryFactory, + juggler, + repository, +} from '@loopback/repository'; +import { + DefaultTransactionalUserModifyRepository, + IAuthUserWithPermissions, +} from '@sourceloop/core'; +import {AuthenticationBindings} from 'loopback4-authentication'; + +import { + Address, + Contact, + Lead, + Resource, + Tenant, + TenantRelations, +} from '../models'; +import {ContactRepository} from './contact.repository'; +import {LeadRepository} from './lead.repository'; +import {ResourceRepository} from './resource.repository'; +import {AddressRepository} from './address.repository'; +import {TenantManagementDbSourceName} from '../types'; + +export class SaasTenantRepository extends DefaultTransactionalUserModifyRepository< + Tenant, + typeof Tenant.prototype.id, + TenantRelations +> { + public readonly contacts: HasManyRepositoryFactory< + Contact, + typeof Tenant.prototype.id + >; + + public readonly resources: HasManyRepositoryFactory< + Resource, + typeof Tenant.prototype.id + >; + + public readonly lead: BelongsToAccessor; + + public readonly address: BelongsToAccessor< + Address, + typeof Tenant.prototype.id + >; + + constructor( + @inject(`datasources.${TenantManagementDbSourceName}`) + dataSource: juggler.DataSource, + @inject.getter(AuthenticationBindings.CURRENT_USER) + public readonly getCurrentUser: Getter, + @repository.getter('ContactRepository') + protected contactRepositoryGetter: Getter, + @repository.getter('LeadRepository') + protected leadRepositoryGetter: Getter, + @repository.getter('ResourceRepository') + protected resourceRepositoryGetter: Getter, + @repository.getter('AddressRepository') + protected addressRepositoryGetter: Getter, + ) { + super(Tenant, dataSource, getCurrentUser); + this.lead = this.createBelongsToAccessorFor('lead', leadRepositoryGetter); + this.registerInclusionResolver('lead', this.lead.inclusionResolver); + this.contacts = this.createHasManyRepositoryFactoryFor( + 'contacts', + contactRepositoryGetter, + ); + this.registerInclusionResolver('contacts', this.contacts.inclusionResolver); + + this.resources = this.createHasManyRepositoryFactoryFor( + 'resources', + resourceRepositoryGetter, + ); + this.registerInclusionResolver( + 'resources', + this.resources.inclusionResolver, + ); + + this.address = this.createBelongsToAccessorFor( + 'address', + addressRepositoryGetter, + ); + this.registerInclusionResolver('address', this.address.inclusionResolver); + } +} + diff --git a/services/tenant-management-service/src/repositories/tenant-config.repository.ts b/services/tenant-management-service/src/repositories/tenant-config.repository.ts new file mode 100644 index 0000000..752f325 --- /dev/null +++ b/services/tenant-management-service/src/repositories/tenant-config.repository.ts @@ -0,0 +1,37 @@ +import {Getter, inject} from '@loopback/core'; +import {juggler, repository, BelongsToAccessor} from '@loopback/repository'; +import {TenantConfig, Tenant} from '../models'; +import { + DefaultUserModifyCrudRepository, + IAuthUserWithPermissions, +} from '@sourceloop/core'; +import {SYSTEM_USER} from '../keys'; +import {TenantManagementDbSourceName} from '../types'; +import {TenantRepository} from './tenant.repository'; + +export class TenantConfigRepository extends DefaultUserModifyCrudRepository< + TenantConfig, + typeof TenantConfig.prototype.id, + {} +> { + public readonly tenant: BelongsToAccessor< + Tenant, + typeof TenantConfig.prototype.id + >; + + constructor( + @inject.getter(SYSTEM_USER) + public readonly getCurrentUser: Getter, + @inject(`datasources.${TenantManagementDbSourceName}`) + dataSource: juggler.DataSource, + @repository.getter('TenantRepository') + protected tenantRepositoryGetter: Getter, + ) { + super(TenantConfig, dataSource, getCurrentUser); + this.tenant = this.createBelongsToAccessorFor( + 'tenant', + tenantRepositoryGetter, + ); + this.registerInclusionResolver('tenant', this.tenant.inclusionResolver); + } +} diff --git a/services/tenant-management-service/src/services/lead-authenticator.service.ts b/services/tenant-management-service/src/services/lead-authenticator.service.ts index ee2c700..1208fbe 100644 --- a/services/tenant-management-service/src/services/lead-authenticator.service.ts +++ b/services/tenant-management-service/src/services/lead-authenticator.service.ts @@ -43,6 +43,18 @@ export class LeadAuthenticator { PermissionKey.ViewPlan, PermissionKey.ViewSubscription, PermissionKey.CreateInvoice, + PermissionKey.CreateBillingCustomer, + PermissionKey.CreateBillingPaymentSource, + PermissionKey.CreateBillingInvoice, + PermissionKey.GetBillingCustomer, + PermissionKey.GetBillingPaymentSource, + PermissionKey.GetBillingInvoice, + PermissionKey.UpdateBillingCustomer, + PermissionKey.UpdateBillingPaymentSource, + PermissionKey.UpdateBillingInvoice, + PermissionKey.DeleteBillingCustomer, + PermissionKey.DeleteBillingPaymentSource, + PermissionKey.DeleteBillingInvoice, ]); const randomKey = this.cryptoHelperService.generateRandomString( +process.env.LEAD_KEY_LENGTH!, diff --git a/services/tenant-management-service/src/services/onboarding.service.ts b/services/tenant-management-service/src/services/onboarding.service.ts index 2e11ea6..1d444c5 100644 --- a/services/tenant-management-service/src/services/onboarding.service.ts +++ b/services/tenant-management-service/src/services/onboarding.service.ts @@ -2,7 +2,9 @@ import {BindingScope, inject, injectable, service} from '@loopback/core'; import {repository} from '@loopback/repository'; import {HttpErrors} from '@loopback/rest'; import {ILogger, LOGGER} from '@sourceloop/core'; +import {TenantStatus} from '../enums'; import {Address, Contact, Lead, Tenant, TenantOnboardDTO} from '../models'; +import {CreateLeadDTO} from '../models/dtos/create-lead-dto.model'; import { AddressRepository, ContactRepository, @@ -10,10 +12,8 @@ import { TenantRepository, } from '../repositories'; import {LeadUser} from '../types'; -import {LeadAuthenticator} from './lead-authenticator.service'; -import {TenantStatus} from '../enums'; -import {CreateLeadDTO} from '../models/dtos/create-lead-dto.model'; import {hasAnyOf, weakEqual} from '../utils'; +import {LeadAuthenticator} from './lead-authenticator.service'; /** * Helper service for onboarding tenants. @@ -197,8 +197,21 @@ export class OnboardingService { }, {transaction}, ); + const res = await this.tenantRepository.findById( + tenant.id, + { + include: [ + {relation: 'contacts'}, + {relation: 'resources'}, + {relation: 'lead'}, + {relation: 'address'}, + ], + }, + {transaction}, + ); + await transaction.commit(); - return tenant; + return res; } catch (error) { await transaction.rollback(); throw error; diff --git a/services/tenant-management-service/src/services/webhook/provisioning-webhook.handler.ts b/services/tenant-management-service/src/services/webhook/provisioning-webhook.handler.ts index 402b4f5..ce7e305 100644 --- a/services/tenant-management-service/src/services/webhook/provisioning-webhook.handler.ts +++ b/services/tenant-management-service/src/services/webhook/provisioning-webhook.handler.ts @@ -15,6 +15,7 @@ import { } from '../../types'; import {CryptoHelperService} from '../crypto-helper.service'; import {NotificationService} from '../notifications'; +import { SaasTenantRepository } from '../../repositories/saas-tenant.repository'; /** * Handler for provisioning webhooks. @@ -37,7 +38,7 @@ export class ProvisioningWebhookHandler implements IWebhookHandler { constructor( @repository(ResourceRepository) public resourceRepository: ResourceRepository, - @repository(TenantRepository) + @repository(SaasTenantRepository) public tenantRepository: TenantRepository, @inject('services.NotificationService') private notificationService: NotificationService, diff --git a/services/tenant-management-service/src/types/i-idp.interface.ts b/services/tenant-management-service/src/types/i-idp.interface.ts new file mode 100644 index 0000000..e381dbf --- /dev/null +++ b/services/tenant-management-service/src/types/i-idp.interface.ts @@ -0,0 +1,14 @@ +import {TenantDto} from '../models/dtos/tenant-dto.model'; + +export enum IdPKey { + AUTH0 = 'auth0', + COGNITO = 'cognito', + KEYCLOAK = 'keycloak', +} + +export type ConfigureIdpFunc = (payload: IdpDetails) => Promise; + +export interface IdpDetails { + identityProvider: IdPKey; + tenant: TenantDto; +} diff --git a/services/tenant-management-service/src/types/index.ts b/services/tenant-management-service/src/types/index.ts index 9cea043..3ac555b 100644 --- a/services/tenant-management-service/src/types/index.ts +++ b/services/tenant-management-service/src/types/index.ts @@ -38,3 +38,4 @@ export * from './resource.type'; export * from './i-provisioning-service.interface'; export * from './i-subscription.interface'; export * from './i-event-connector.interface'; +export * from './i-idp.interface'; diff --git a/services/tenant-management-service/src/webhook.component.ts b/services/tenant-management-service/src/webhook.component.ts index 959e5f7..5fa722f 100644 --- a/services/tenant-management-service/src/webhook.component.ts +++ b/services/tenant-management-service/src/webhook.component.ts @@ -35,7 +35,11 @@ import { WEBHOOK_VERIFIER, } from './keys'; import {ITenantManagementServiceConfig} from './types'; -import {WebhookController} from './controllers'; +import { + TenantConfigController, + TenantConfigTenantController, + WebhookController, +} from './controllers'; import { Address, Contact, @@ -50,6 +54,7 @@ import { TenantOnboardDTO, VerifyLeadResponseDTO, WebhookDTO, + TenantConfig, } from './models'; import { AddressRepository, @@ -60,9 +65,11 @@ import { ResourceRepository, TenantRepository, WebhookSecretRepository, + SaasTenantRepository, + TenantConfigRepository, } from './repositories'; import {WebhookVerifierProvider} from './interceptors'; -import {SystemUserProvider} from './providers'; +import {KeycloakIdpProvider, SystemUserProvider} from './providers'; import {CryptoHelperService, NotificationService} from './services'; import { DEFAULT_SIGNATURE_HEADER, @@ -70,6 +77,9 @@ import { DEFAULT_TIMESTAMP_TOLERANCE, } from './utils'; import {ProvisioningWebhookHandler} from './services/webhook'; +import {KeycloakIdpProvider} from './providers/idp/idp-keycloak.provider'; +import {IdpController} from './controllers/idp.controller'; +import {Auth0IdpProvider} from './providers/idp'; export class WebhookTenantManagementServiceComponent implements Component { constructor( @@ -109,7 +119,9 @@ export class WebhookTenantManagementServiceComponent implements Component { LeadRepository, ResourceRepository, TenantRepository, + SaasTenantRepository, WebhookSecretRepository, + TenantConfigRepository, ]; this.models = [ @@ -126,12 +138,24 @@ export class WebhookTenantManagementServiceComponent implements Component { TenantOnboardDTO, VerifyLeadResponseDTO, WebhookDTO, + TenantConfig, ]; - this.controllers = [WebhookController]; + this.controllers = [ + WebhookController, + IdpController, + TenantConfigController, + TenantConfigTenantController, + ]; this.bindings = [ Binding.bind(WEBHOOK_VERIFIER).toProvider(WebhookVerifierProvider), + Binding.bind(TenantManagementServiceBindings.IDP_KEYCLOAK).toProvider( + KeycloakIdpProvider, + ), + Binding.bind(TenantManagementServiceBindings.IDP_AUTH0).toProvider( + Auth0IdpProvider, + ), Binding.bind(SYSTEM_USER).toProvider(SystemUserProvider), Binding.bind(WEBHOOK_CONFIG).to({ signatureHeaderName: DEFAULT_SIGNATURE_HEADER,