diff --git a/package-lock.json b/package-lock.json index 659cf70..def974b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "cacheable": "^1.8.5", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "firebase-admin": "^13.0.2", "google-libphonenumber": "^3.2.39", "handlebars": "^4.7.8", "ioredis": "^5.4.1", @@ -881,11 +882,251 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-3.1.1.tgz", + "integrity": "sha512-5DGmA8FTdB2XbDeEwc/5ZXBl6UbBAyBOOLlPuBnZ/N1SwdH9Ii+cOX3tBROlDgcTXxjOYnLMVoKk9+FXAw0CJw==", + "license": "MIT" + }, + "node_modules/@firebase/app-check-interop-types": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz", + "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/app-types": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", + "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz", + "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==", + "license": "Apache-2.0" + }, + "node_modules/@firebase/component": { + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.11.tgz", + "integrity": "sha512-eQbeCgPukLgsKD0Kw5wQgsMDX5LeoI1MIrziNDjmc6XDq5ZQnuUymANQgAb2wp1tSF9zDSXyxJmIUXaKgN58Ug==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/util": "1.10.2", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.10.tgz", + "integrity": "sha512-sWp2g92u7xT4BojGbTXZ80iaSIaL6GAL0pwvM0CO/hb0nHSnABAqsH7AhnWGsGvXuEvbPr7blZylPaR9J+GSuQ==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-check-interop-types": "0.3.3", + "@firebase/auth-interop-types": "0.2.4", + "@firebase/component": "0.6.11", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.1.tgz", + "integrity": "sha512-IsFivOjdE1GrjTeKoBU/ZMenESKDXidFDzZzHBPQ/4P20ptGdrl3oLlWrV/QJqJ9lND4IidE3z4Xr5JyfUW1vg==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/component": "0.6.11", + "@firebase/database": "1.0.10", + "@firebase/database-types": "1.0.7", + "@firebase/logger": "0.4.4", + "@firebase/util": "1.10.2", + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.7.tgz", + "integrity": "sha512-I7zcLfJXrM0WM+ksFmFdAMdlq/DFmpeMNa+/GNsLyFo5u/lX5zzkPzGe3srVWqaBQBY5KprylDGxOsP6ETfL0A==", + "license": "Apache-2.0", + "dependencies": { + "@firebase/app-types": "0.9.3", + "@firebase/util": "1.10.2" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz", + "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.10.2.tgz", + "integrity": "sha512-qnSHIoE9FK+HYnNhTI8q14evyqbc/vHRivfB4TgCIUOl4tosmKSQlp7ltymOlMP4xVIJTg5wrkfcZ60X4nUf7Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@golevelup/ts-jest": { "version": "0.6.0", "dev": true, "license": "MIT" }, + "node_modules/@google-cloud/firestore": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-7.11.0.tgz", + "integrity": "sha512-88uZ+jLsp1aVMj7gh3EKYH1aulTAMFAp8sH/v5a9w8q8iqSG27RiWLoxSAFr/XocZ9hGiWH1kEnBw+zl3xAgNA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@opentelemetry/api": "^1.3.0", + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^4.3.3", + "protobufjs": "^7.2.6" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", + "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-7.14.0.tgz", + "integrity": "sha512-H41bPL2cMfSi4EEnFzKvg7XSb7T67ocSXrmF7MPjfgFB0L6CKGzfIYJheAZi1iqXjz6XaCT1OBf6HCG5vDBTOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "duplexify": "^4.1.3", + "fast-xml-parser": "^4.4.1", + "gaxios": "^6.0.2", + "google-auth-library": "^9.6.3", + "html-entities": "^2.5.2", + "mime": "^3.0.0", + "p-limit": "^3.0.1", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/storage/node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "license": "MIT", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.12.5", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.12.5.tgz", + "integrity": "sha512-d3iiHxdpg5+ZcJ6jnDSOT8Z0O0VMVGy34jAnYLUX8yd36b1qn8f1TwOA/Lc7TsOh03IkPJ38eGI5qD2EjNkoEA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/@hamid/hello": { "resolved": "../libraries/test-package", "link": true @@ -1480,6 +1721,17 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "optional": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, "node_modules/@keyv/redis": { "version": "4.0.2", "license": "MIT", @@ -2166,6 +2418,16 @@ "license": "MIT", "optional": true }, + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "license": "MIT", @@ -2174,6 +2436,80 @@ "node": ">=14" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause", + "optional": true + }, "node_modules/@redis/bloom": { "version": "1.2.0", "license": "MIT", @@ -2267,6 +2603,16 @@ "version": "1.2.5", "license": "MIT" }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "devOptional": true, @@ -2334,13 +2680,19 @@ }, "node_modules/@types/body-parser": { "version": "1.19.5", - "dev": true, "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" } }, + "node_modules/@types/caseless": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", + "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", + "license": "MIT", + "optional": true + }, "node_modules/@types/cls-hooked": { "version": "4.3.9", "license": "MIT", @@ -2350,7 +2702,6 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "dev": true, "license": "MIT", "dependencies": { "@types/node": "*" @@ -2426,7 +2777,6 @@ }, "node_modules/@types/http-errors": { "version": "2.0.4", - "dev": true, "license": "MIT" }, "node_modules/@types/isomorphic-fetch": { @@ -2484,6 +2834,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "license": "MIT", + "optional": true + }, "node_modules/@types/methods": { "version": "1.1.4", "dev": true, @@ -2491,7 +2848,6 @@ }, "node_modules/@types/mime": { "version": "1.3.5", - "dev": true, "license": "MIT" }, "node_modules/@types/mjml": { @@ -2580,14 +2936,41 @@ }, "node_modules/@types/qs": { "version": "6.9.17", - "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "dev": true, "license": "MIT" }, + "node_modules/@types/request": { + "version": "2.48.12", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.12.tgz", + "integrity": "sha512-G3sY+NpsA9jnwm0ixhAFQSJ3Q9JkpLZpJbI3GMv0mIAT0y3mRabYeINzal5WOChIiaTEGQYlHOKgkaM9EisWHw==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "node_modules/@types/request/node_modules/form-data": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.2.tgz", + "integrity": "sha512-GgwY0PS7DbXqajuGf4OYlsrIu3zgxD6Vvql43IBhm6MahqA5SK/7mwhtNj2AdH2z35YR34ujJ7BN+3fFC3jP5Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/@types/semver": { "version": "7.5.8", "dev": true, @@ -2595,7 +2978,6 @@ }, "node_modules/@types/send": { "version": "0.17.4", - "dev": true, "license": "MIT", "dependencies": { "@types/mime": "^1", @@ -2604,7 +2986,6 @@ }, "node_modules/@types/serve-static": { "version": "1.15.7", - "dev": true, "license": "MIT", "dependencies": { "@types/http-errors": "*", @@ -2644,6 +3025,13 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "license": "MIT", + "optional": true + }, "node_modules/@types/uuid": { "version": "10.0.0", "dev": true, @@ -3014,12 +3402,25 @@ }, "node_modules/abbrev": { "version": "2.0.0", - "devOptional": true, "license": "ISC", + "optional": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accept-language-parser": { "version": "1.5.0", "license": "MIT" @@ -3330,6 +3731,16 @@ "node": ">=8" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asap": { "version": "2.0.6", "devOptional": true, @@ -3369,6 +3780,16 @@ "node": "^4.7 || >=6.9 || >=7.3" } }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "license": "MIT", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, "node_modules/asynckit": { "version": "0.4.0", "license": "MIT" @@ -3558,6 +3979,15 @@ "tweetnacl": "^0.14.3" } }, + "node_modules/bignumber.js": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.2.tgz", + "integrity": "sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "license": "MIT", @@ -3845,6 +4275,36 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "license": "MIT", + "optional": true, + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "dev": true, @@ -4887,6 +5347,48 @@ "node": ">=12" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "license": "MIT" @@ -5044,11 +5546,10 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, "engines": { "node": ">= 0.4" } @@ -5065,6 +5566,18 @@ "dev": true, "license": "MIT" }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-promise": { "version": "4.2.6", "license": "MIT" @@ -5353,6 +5866,16 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/eventemitter2": { "version": "6.4.9", "license": "MIT" @@ -5474,6 +5997,12 @@ "version": "0.1.10", "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/extend-object": { "version": "1.0.0", "license": "MIT", @@ -5499,13 +6028,22 @@ ], "license": "MIT" }, + "node_modules/farmhash-modern": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/farmhash-modern/-/farmhash-modern-1.1.0.tgz", + "integrity": "sha512-6ypT4XfgqJk/F3Yuv4SX26I3doUjt0GTG4a+JgWxXQpxXzTBq8fPUeGHfcYMMDPHJHm3yPOSjaeBwBGAHWXCdA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/fast-copy": { "version": "3.0.2", "license": "MIT" }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -5549,6 +6087,29 @@ "version": "2.1.1", "license": "MIT" }, + "node_modules/fast-xml-parser": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.5.1.tgz", + "integrity": "sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.17.1", "dev": true, @@ -5557,6 +6118,18 @@ "reusify": "^1.0.4" } }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "license": "Apache-2.0", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "dev": true, @@ -5669,6 +6242,59 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/firebase-admin": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-13.0.2.tgz", + "integrity": "sha512-YWVpoN+tZVSRXF0qC0gojoF5bSqvBRbnBk8+xUtFiguM2L4vB7f0moAwV1VVWDDHvTnvQ68OyTMpdp6wKo/clw==", + "license": "Apache-2.0", + "dependencies": { + "@fastify/busboy": "^3.0.0", + "@firebase/database-compat": "^2.0.0", + "@firebase/database-types": "^1.0.6", + "@types/node": "^22.8.7", + "farmhash-modern": "^1.1.0", + "google-auth-library": "^9.14.2", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.1.0", + "node-forge": "^1.3.1", + "uuid": "^11.0.2" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^7.11.0", + "@google-cloud/storage": "^7.14.0" + } + }, + "node_modules/firebase-admin/node_modules/@types/node": { + "version": "22.10.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", + "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/firebase-admin/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "license": "MIT" + }, + "node_modules/firebase-admin/node_modules/uuid": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.3.tgz", + "integrity": "sha512-d0z310fCWv5dJwnX1Y/MncBAqGMKEzlBb1AOf7z9K8ALnd0utBX/msg/fA0+sbyN1ihbMsLhrBlnl1ak7Wa0rg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/fixpack": { "version": "4.0.0", "license": "MIT", @@ -5887,6 +6513,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "license": "MIT", + "optional": true + }, "node_modules/gauge": { "version": "3.0.2", "license": "ISC", @@ -5909,6 +6542,57 @@ "version": "3.0.7", "license": "ISC" }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.0.tgz", + "integrity": "sha512-Jh/AIwwgaxan+7ZUUmRLCjtchyDiqh4KjBJ5tW3plBZb5iL/BPcso8A5DlzeD9qlw0duCamnNdpFjxwaT0KyKg==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/generic-pool": { "version": "3.9.0", "license": "MIT", @@ -5932,14 +6616,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -6054,6 +6745,68 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.0.tgz", + "integrity": "sha512-7ccSEJFDFO7exFbO6NRyC+xH8/mZ1GZGG2xxx9iHxZWcjUjJpjWxIMw3cofAKcueZ6DATiukmmprD7yavQHOyQ==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-auth-library/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-auth-library/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/google-gax": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.4.1.tgz", + "integrity": "sha512-Phyp9fMfA00J3sZbJxbbB4jC55b7DBjE3F6poyL3wKMEBVKA79q6BGuHcTiM28yOzVql0NDbRL8MLLh8Iwk9Dg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "^1.10.9", + "@grpc/proto-loader": "^0.7.13", + "@types/long": "^4.0.0", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "google-auth-library": "^9.3.0", + "node-fetch": "^2.7.0", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^2.0.2", + "protobufjs": "^7.3.2", + "retry-request": "^7.0.0", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/google-libphonenumber": { "version": "3.2.39", "license": "(MIT AND Apache-2.0)", @@ -6062,10 +6815,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "get-intrinsic": "^1.1.3" + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -6081,6 +6836,40 @@ "dev": true, "license": "MIT" }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gtoken/node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/gtoken/node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/handlebars": { "version": "4.7.8", "license": "MIT", @@ -6132,18 +6921,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { - "version": "1.0.3", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6211,6 +6992,23 @@ "version": "1.5.1", "license": "MIT" }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT", + "optional": true + }, "node_modules/html-escaper": { "version": "2.0.2", "dev": true, @@ -6288,6 +7086,27 @@ "node": ">= 0.8" } }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/http-signature": { "version": "1.3.1", "license": "MIT", @@ -6419,8 +7238,8 @@ }, "node_modules/ini": { "version": "1.3.8", - "devOptional": true, - "license": "ISC" + "license": "ISC", + "optional": true }, "node_modules/inquirer": { "version": "8.2.6", @@ -6600,12 +7419,16 @@ "optional": true }, "node_modules/is-regex": { - "version": "1.1.4", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "license": "MIT", "optional": true, "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -6616,7 +7439,6 @@ }, "node_modules/is-stream": { "version": "2.0.1", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7422,6 +8244,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/joycon": { "version": "3.1.1", "license": "MIT", @@ -7492,6 +8323,15 @@ "node": ">=6" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "license": "MIT" @@ -7620,6 +8460,56 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwks-rsa": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.1.0.tgz", + "integrity": "sha512-v7nqlfezb9YfHHzYII3ef2a2j1XnGeSE/bK3WfumaYCqONAIstJbrEGapz4kadScZzEt7zYCN7bucj8C0Mv/Rg==", + "license": "MIT", + "dependencies": { + "@types/express": "^4.17.17", + "@types/jsonwebtoken": "^9.0.2", + "debug": "^4.3.4", + "jose": "^4.14.6", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { + "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==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/jwks-rsa/node_modules/@types/jsonwebtoken": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz", + "integrity": "sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/jws": { "version": "3.2.2", "license": "MIT", @@ -7715,6 +8605,11 @@ "node": ">=10" } }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "dev": true, @@ -8074,11 +8969,16 @@ "version": "4.17.21", "license": "MIT" }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT", + "optional": true + }, "node_modules/lodash.clonedeep": { "version": "4.5.0", - "license": "MIT", - "optional": true, - "peer": true + "license": "MIT" }, "node_modules/lodash.defaults": { "version": "4.2.0", @@ -8291,6 +9191,13 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/lower-case": { "version": "1.1.4", "license": "MIT", @@ -8304,6 +9211,34 @@ "yallist": "^3.0.2" } }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "license": "MIT", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/magic-string": { "version": "0.30.8", "dev": true, @@ -8428,6 +9363,15 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "license": "MIT", @@ -9140,6 +10084,15 @@ } } }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "license": "(BSD-3-Clause OR GPL-2.0)", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "dev": true, @@ -9159,8 +10112,8 @@ }, "node_modules/nopt": { "version": "7.2.1", - "devOptional": true, "license": "ISC", + "optional": true, "dependencies": { "abbrev": "^2.0.0" }, @@ -11978,6 +12931,16 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.3", "license": "MIT", @@ -13475,7 +14438,7 @@ }, "node_modules/p-limit": { "version": "3.1.0", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -14106,6 +15069,44 @@ "license": "ISC", "optional": true }, + "node_modules/proto3-json-serializer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", + "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "protobufjs": "^7.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "license": "MIT", @@ -14691,6 +15692,31 @@ "dev": true, "license": "ISC" }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", + "optional": true, + "dependencies": { + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/reusify": { "version": "1.0.4", "dev": true, @@ -15320,6 +16346,23 @@ "node": ">= 0.8" } }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "license": "MIT", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT", + "optional": true + }, "node_modules/streamsearch": { "version": "1.1.0", "engines": { @@ -15441,6 +16484,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "license": "MIT", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "license": "MIT", + "optional": true + }, "node_modules/superagent": { "version": "9.0.2", "dev": true, @@ -15563,6 +16620,23 @@ "version": "4.0.0", "license": "ISC" }, + "node_modules/teeny-request": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.9", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/terser": { "version": "5.36.0", "dev": true, @@ -16580,6 +17654,29 @@ "node": ">=4.0" } }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "license": "Apache-2.0", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/whatwg-fetch": { "version": "3.6.20", "license": "MIT" @@ -16763,7 +17860,7 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/package.json b/package.json index 4ca1dd4..69fc180 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "cacheable": "^1.8.5", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", + "firebase-admin": "^13.0.2", "google-libphonenumber": "^3.2.39", "handlebars": "^4.7.8", "ioredis": "^5.4.1", diff --git a/src/app.module.ts b/src/app.module.ts index a65c485..c042142 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -10,6 +10,7 @@ import { AllowanceModule } from './allowance/allowance.module'; import { AuthModule } from './auth/auth.module'; import { CacheModule } from './common/modules/cache/cache.module'; import { LookupModule } from './common/modules/lookup/lookup.module'; +import { NotificationModule } from './common/modules/notification/notification.module'; import { OtpModule } from './common/modules/otp/otp.module'; import { AllExceptionsFilter, buildI18nValidationExceptionFilter } from './core/filters'; import { buildConfigOptions, buildLoggerOptions, buildTypeormOptions } from './core/module-options'; @@ -70,6 +71,8 @@ import { TaskModule } from './task/task.module'; LookupModule, HealthModule, + + NotificationModule, ], providers: [ // Global Pipes diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index 0b99f05..a051a91 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -19,6 +19,6 @@ import { AccessTokenStrategy } from './strategies'; ], providers: [AuthService, UserRepository, UserService, DeviceService, DeviceRepository, AccessTokenStrategy], controllers: [AuthController], - exports: [UserService], + exports: [UserService, DeviceService], }) export class AuthModule {} diff --git a/src/auth/dtos/request/login.request.dto.ts b/src/auth/dtos/request/login.request.dto.ts index 9599396..d089d56 100644 --- a/src/auth/dtos/request/login.request.dto.ts +++ b/src/auth/dtos/request/login.request.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsEnum, IsString, ValidateIf } from 'class-validator'; +import { IsEmail, IsEnum, IsNotEmpty, IsOptional, IsString, ValidateIf } from 'class-validator'; import { i18nValidationMessage as i18n } from 'nestjs-i18n'; import { GrantType } from '~/auth/enums'; export class LoginRequestDto { @@ -17,8 +17,14 @@ export class LoginRequestDto { @ValidateIf((o) => o.grantType === GrantType.PASSWORD) password!: string; - @ApiProperty({ example: 'device-token' }) - @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.deviceToken' }) }) + @ApiProperty({ example: 'fcm-device-token' }) + @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.fcmToken' }) }) + @IsNotEmpty({ message: i18n('validation.IsNotEmpty', { path: 'general', property: 'auth.fcmToken' }) }) + @IsOptional() + fcmToken?: string; + + @ApiProperty({ example: 'Login signature' }) + @IsString({ message: i18n('validation.IsString', { path: 'general', property: 'auth.signature' }) }) @ValidateIf((o) => o.grantType === GrantType.BIOMETRIC) - deviceToken!: string; + signature!: string; } diff --git a/src/auth/entities/device.entity.ts b/src/auth/entities/device.entity.ts index 3042b70..111a398 100644 --- a/src/auth/entities/device.entity.ts +++ b/src/auth/entities/device.entity.ts @@ -15,6 +15,9 @@ export class Device { @Column('varchar', { name: 'public_key', nullable: true }) publicKey?: string | null; + @Column('varchar', { name: 'fcm_token', nullable: true }) + fcmToken?: string | null; + @Column('timestamp with time zone', { name: 'last_access_on', default: () => 'CURRENT_TIMESTAMP' }) lastAccessOn!: Date; diff --git a/src/auth/entities/user.entity.ts b/src/auth/entities/user.entity.ts index 7b4a44d..4c63f15 100644 --- a/src/auth/entities/user.entity.ts +++ b/src/auth/entities/user.entity.ts @@ -8,6 +8,7 @@ import { PrimaryGeneratedColumn, UpdateDateColumn, } from 'typeorm'; +import { Notification } from '~/common/modules/notification/entities'; import { Otp } from '~/common/modules/otp/entities'; import { Customer } from '~/customer/entities/customer.entity'; import { Roles } from '../enums'; @@ -54,6 +55,9 @@ export class User extends BaseEntity { @OneToMany(() => Device, (device) => device.user) devices!: Device[]; + @OneToMany(() => Notification, (notification) => notification.user) + notifications!: Notification[]; + @CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone' }) createdAt!: Date; diff --git a/src/auth/repositories/device.repository.ts b/src/auth/repositories/device.repository.ts index d9cc374..9033437 100644 --- a/src/auth/repositories/device.repository.ts +++ b/src/auth/repositories/device.repository.ts @@ -1,6 +1,6 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; +import { IsNull, Not, Repository } from 'typeorm'; import { Device } from '../entities'; @Injectable() @@ -16,6 +16,10 @@ export class DeviceRepository { } updateDevice(deviceId: string, data: Partial) { - return this.deviceRepository.update({ deviceId }, data); + return this.deviceRepository.save({ deviceId, ...data }); + } + + getTokens(userId: string) { + return this.deviceRepository.find({ where: { userId, fcmToken: Not(IsNull()) }, select: ['fcmToken'] }); } } diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index 9b9f162..3b33963 100644 --- a/src/auth/services/auth.service.ts +++ b/src/auth/services/auth.service.ts @@ -187,7 +187,11 @@ export class AuthService { tokens = await this.loginWithBiometric(loginDto, user, deviceId); } - this.deviceService.updateDevice(deviceId, { lastAccessOn: new Date() }); + this.deviceService.updateDevice(deviceId, { + lastAccessOn: new Date(), + fcmToken: loginDto.fcmToken, + userId: user.id, + }); return [tokens, user]; } @@ -245,7 +249,7 @@ export class AuthService { throw new UnauthorizedException('AUTH.BIOMETRIC_NOT_ENABLED'); } - const cleanToken = removePadding(loginDto.deviceToken); + const cleanToken = removePadding(loginDto.signature); const isValidToken = await verifySignature( device.publicKey, cleanToken, diff --git a/src/auth/services/device.service.ts b/src/auth/services/device.service.ts index a6b4266..d6f3576 100644 --- a/src/auth/services/device.service.ts +++ b/src/auth/services/device.service.ts @@ -16,4 +16,10 @@ export class DeviceService { updateDevice(deviceId: string, data: Partial) { return this.deviceRepository.updateDevice(deviceId, data); } + + async getTokens(userId: string): Promise { + const devices = await this.deviceRepository.getTokens(userId); + + return devices.map((device) => device.fcmToken!); + } } diff --git a/src/common/modules/notification/controllers/index.ts b/src/common/modules/notification/controllers/index.ts new file mode 100644 index 0000000..d24ede8 --- /dev/null +++ b/src/common/modules/notification/controllers/index.ts @@ -0,0 +1 @@ +export * from './notifications.controller'; diff --git a/src/common/modules/notification/controllers/notifications.controller.ts b/src/common/modules/notification/controllers/notifications.controller.ts new file mode 100644 index 0000000..a862315 --- /dev/null +++ b/src/common/modules/notification/controllers/notifications.controller.ts @@ -0,0 +1,36 @@ +import { Controller, Get, HttpCode, HttpStatus, Post, Query, UseGuards } from '@nestjs/common'; +import { ApiBearerAuth, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { IJwtPayload } from '~/auth/interfaces'; +import { AuthenticatedUser } from '~/common/decorators'; +import { AccessTokenGuard } from '~/common/guards'; +import { PageOptionsRequestDto } from '~/core/dtos'; +import { NotificationsPageResponseDto } from '../dtos/response'; +import { NotificationsService } from '../services/notifications.service'; + +@Controller('notifications') +@ApiTags('Notifications') +@ApiBearerAuth() +export class NotificationsController { + constructor(private readonly notificationsService: NotificationsService) {} + + @Get() + @UseGuards(AccessTokenGuard) + @ApiResponse({ type: NotificationsPageResponseDto }) + async getNotifications(@AuthenticatedUser() { sub }: IJwtPayload, @Query() pageOptionsDto: PageOptionsRequestDto) { + const { notifications, count, unreadCount } = await this.notificationsService.getNotifications(sub, pageOptionsDto); + + return new NotificationsPageResponseDto(notifications, { + itemCount: count, + unreadCount, + page: pageOptionsDto.page, + size: pageOptionsDto.size, + }); + } + + @Post('mark-as-read') + @UseGuards(AccessTokenGuard) + @HttpCode(HttpStatus.NO_CONTENT) + markAsRead(@AuthenticatedUser() { sub }: IJwtPayload) { + return this.notificationsService.markAsRead(sub); + } +} diff --git a/src/common/modules/notification/dtos/response/index.ts b/src/common/modules/notification/dtos/response/index.ts new file mode 100644 index 0000000..290143c --- /dev/null +++ b/src/common/modules/notification/dtos/response/index.ts @@ -0,0 +1,2 @@ +export * from './notifications-page.response.dto'; +export * from './notifications.response.dto'; diff --git a/src/common/modules/notification/dtos/response/notifications-meta.response.dto.ts b/src/common/modules/notification/dtos/response/notifications-meta.response.dto.ts new file mode 100644 index 0000000..4e04654 --- /dev/null +++ b/src/common/modules/notification/dtos/response/notifications-meta.response.dto.ts @@ -0,0 +1,13 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { PageMetaResponseDto } from '~/core/dtos'; +import { INotificationPageMeta } from '../../interfaces'; + +export class NotificationMetaResponseDto extends PageMetaResponseDto { + @ApiProperty({ example: 4 }) + readonly unreadCount!: number; + + constructor(meta: INotificationPageMeta) { + super(meta); + this.unreadCount = meta.unreadCount; + } +} diff --git a/src/common/modules/notification/dtos/response/notifications-page.response.dto.ts b/src/common/modules/notification/dtos/response/notifications-page.response.dto.ts new file mode 100644 index 0000000..6f89a6b --- /dev/null +++ b/src/common/modules/notification/dtos/response/notifications-page.response.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Notification } from '../../entities'; +import { INotificationPageMeta } from '../../interfaces'; +import { NotificationMetaResponseDto } from './notifications-meta.response.dto'; +import { NotificationsResponseDto } from './notifications.response.dto'; + +export class NotificationsPageResponseDto { + @ApiProperty({ type: [NotificationsResponseDto] }) + data!: NotificationsResponseDto[]; + + @ApiProperty({ type: NotificationMetaResponseDto }) + meta: INotificationPageMeta; + + constructor(data: Notification[], meta: INotificationPageMeta) { + this.data = data.map((notification) => new NotificationsResponseDto(notification)); + this.meta = new NotificationMetaResponseDto(meta); + } +} diff --git a/src/common/modules/notification/dtos/response/notifications.response.dto.ts b/src/common/modules/notification/dtos/response/notifications.response.dto.ts new file mode 100644 index 0000000..1f48591 --- /dev/null +++ b/src/common/modules/notification/dtos/response/notifications.response.dto.ts @@ -0,0 +1,28 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Notification } from '../../entities'; +import { NotificationStatus } from '../../enums'; + +export class NotificationsResponseDto { + @ApiProperty({ example: 'f1b1b9b1-1b1b-1b1b-1b1b-1b1b1b1b1b1b' }) + id!: string; + + @ApiProperty({ example: 'Test' }) + title: string; + + @ApiProperty({ example: 'TestBody' }) + body: string; + + @ApiProperty({ example: NotificationStatus.UNREAD }) + status!: NotificationStatus; + + @ApiProperty({ example: '2021-09-01T00:00:00.000Z' }) + createdAt!: Date; + + constructor(notification: Notification) { + this.id = notification.id; + this.title = notification.title; + this.body = notification.message; + this.status = notification.status!; + this.createdAt = notification.createdAt; + } +} diff --git a/src/common/modules/notification/entities/index.ts b/src/common/modules/notification/entities/index.ts new file mode 100644 index 0000000..b95a361 --- /dev/null +++ b/src/common/modules/notification/entities/index.ts @@ -0,0 +1 @@ +export * from './notification.entity'; diff --git a/src/common/modules/notification/entities/notification.entity.ts b/src/common/modules/notification/entities/notification.entity.ts new file mode 100644 index 0000000..cff5d42 --- /dev/null +++ b/src/common/modules/notification/entities/notification.entity.ts @@ -0,0 +1,48 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { User } from '~/auth/entities'; +import { NotificationChannel, NotificationScope, NotificationStatus } from '../enums'; + +@Entity('notifications') +export class Notification { + @PrimaryGeneratedColumn('uuid') + id!: string; + + @Column('varchar', { name: 'title' }) + title!: string; + + @Column('varchar', { name: 'message' }) + message!: string; + + @Column('varchar', { name: 'recipient', nullable: true }) + recipient?: string | null; + + @Column('varchar', { name: 'scope' }) + scope!: NotificationScope; + + @Column('varchar', { name: 'status', nullable: true }) + status!: NotificationStatus | null; + + @Column('varchar', { name: 'channel' }) + channel!: NotificationChannel; + + @Column('uuid', { name: 'user_id', nullable: true }) + userId!: string; + + @ManyToOne(() => User, (user) => user.notifications, { onDelete: 'CASCADE', nullable: true }) + @JoinColumn({ name: 'user_id' }) + user!: User; + + @CreateDateColumn({ name: 'created_at', type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' }) + createdAt!: Date; + + @UpdateDateColumn({ name: 'updated_at', type: 'timestamp with time zone', default: () => 'CURRENT_TIMESTAMP' }) + updatedAt!: Date; +} diff --git a/src/common/modules/notification/enums/index.ts b/src/common/modules/notification/enums/index.ts new file mode 100644 index 0000000..1e495a1 --- /dev/null +++ b/src/common/modules/notification/enums/index.ts @@ -0,0 +1,3 @@ +export * from './notification-channel.enum'; +export * from './notification-scope.enum'; +export * from './notification-status.enum'; diff --git a/src/common/modules/notification/enums/notification-channel.enum.ts b/src/common/modules/notification/enums/notification-channel.enum.ts new file mode 100644 index 0000000..931b198 --- /dev/null +++ b/src/common/modules/notification/enums/notification-channel.enum.ts @@ -0,0 +1,5 @@ +export enum NotificationChannel { + EMAIL = 'EMAIL', + SMS = 'SMS', + PUSH = 'PUSH', +} diff --git a/src/common/modules/notification/enums/notification-scope.enum.ts b/src/common/modules/notification/enums/notification-scope.enum.ts new file mode 100644 index 0000000..b8801f2 --- /dev/null +++ b/src/common/modules/notification/enums/notification-scope.enum.ts @@ -0,0 +1,5 @@ +export enum NotificationScope { + USER_REGISTERED = 'USER_REGISTERED', + TASK_COMPLETED = 'TASK_COMPLETED', + GIFT_RECEIVED = 'GIFT_RECEIVED', +} diff --git a/src/common/modules/notification/enums/notification-status.enum.ts b/src/common/modules/notification/enums/notification-status.enum.ts new file mode 100644 index 0000000..a9e55aa --- /dev/null +++ b/src/common/modules/notification/enums/notification-status.enum.ts @@ -0,0 +1,4 @@ +export enum NotificationStatus { + READ = 'READ', + UNREAD = 'UNREAD', +} diff --git a/src/common/modules/notification/interfaces/index.ts b/src/common/modules/notification/interfaces/index.ts new file mode 100644 index 0000000..9042da3 --- /dev/null +++ b/src/common/modules/notification/interfaces/index.ts @@ -0,0 +1 @@ +export * from './notification-page-meta.interface'; diff --git a/src/common/modules/notification/interfaces/notification-page-meta.interface.ts b/src/common/modules/notification/interfaces/notification-page-meta.interface.ts new file mode 100644 index 0000000..a20bc69 --- /dev/null +++ b/src/common/modules/notification/interfaces/notification-page-meta.interface.ts @@ -0,0 +1,5 @@ +import { IPageMeta } from '~/core/dtos'; + +export interface INotificationPageMeta extends IPageMeta { + unreadCount: number; +} diff --git a/src/common/modules/notification/notification.module.ts b/src/common/modules/notification/notification.module.ts new file mode 100644 index 0000000..5494cfa --- /dev/null +++ b/src/common/modules/notification/notification.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AuthModule } from '~/auth/auth.module'; +import { NotificationsController } from './controllers'; +import { Notification } from './entities'; +import { NotificationsRepository } from './repositories'; +import { FirebaseService } from './services/firebase.service'; +import { NotificationsService } from './services/notifications.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Notification]), AuthModule], + providers: [NotificationsService, FirebaseService, NotificationsRepository], + exports: [NotificationsService], + controllers: [NotificationsController], +}) +export class NotificationModule {} diff --git a/src/common/modules/notification/repositories/index.ts b/src/common/modules/notification/repositories/index.ts new file mode 100644 index 0000000..b97171c --- /dev/null +++ b/src/common/modules/notification/repositories/index.ts @@ -0,0 +1 @@ +export * from './notifications.repository'; diff --git a/src/common/modules/notification/repositories/notifications.repository.ts b/src/common/modules/notification/repositories/notifications.repository.ts new file mode 100644 index 0000000..2a1a44c --- /dev/null +++ b/src/common/modules/notification/repositories/notifications.repository.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { PageOptionsRequestDto } from '~/core/dtos'; +import { Notification } from '../entities'; +import { NotificationChannel, NotificationStatus } from '../enums'; +const ONE = 1; +@Injectable() +export class NotificationsRepository { + constructor(@InjectRepository(Notification) private readonly notificationsRepository: Repository) {} + + getNotifications(userId: string, pageOptionsDto: PageOptionsRequestDto) { + const readQuery = this.notificationsRepository.createQueryBuilder('notification'); + readQuery.where('notification.userId = :userId', { userId }); + readQuery.andWhere('notification.channel = :channel', { channel: NotificationChannel.PUSH }); + readQuery.orderBy('notification.createdAt', 'DESC'); + readQuery.skip((pageOptionsDto.page - ONE) * pageOptionsDto.size); + readQuery.take(pageOptionsDto.size); + return readQuery.getManyAndCount(); + } + + getUnreadNotificationsCount(userId: string) { + return this.notificationsRepository.count({ where: { userId, status: NotificationStatus.UNREAD } }); + } + + markAsRead(userId: string) { + return this.notificationsRepository.update( + { userId, status: NotificationStatus.UNREAD }, + { status: NotificationStatus.READ }, + ); + } + + createNotification(notification: Partial) { + return this.notificationsRepository.save(notification); + } +} diff --git a/src/common/modules/notification/services/firebase.service.ts b/src/common/modules/notification/services/firebase.service.ts new file mode 100644 index 0000000..5bc3ebe --- /dev/null +++ b/src/common/modules/notification/services/firebase.service.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import * as admin from 'firebase-admin'; +@Injectable() +export class FirebaseService { + constructor(private readonly configService: ConfigService) { + admin.initializeApp({ + credential: admin.credential.cert({ + projectId: this.configService.get('FIREBASE_PROJECT_ID'), + clientEmail: this.configService.get('FIREBASE_CLIENT_EMAIL'), + privateKey: this.configService.get('FIREBASE_PRIVATE_KEY').replace(/\\n/g, '\n'), + }), + }); + } + + sendNotification(tokens: string | string[], title: string, body: string) { + const message = { + notification: { + title, + body, + }, + tokens: Array.isArray(tokens) ? tokens : [tokens], + }; + + admin.messaging().sendEachForMulticast(message); + } +} diff --git a/src/common/modules/notification/services/index.ts b/src/common/modules/notification/services/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/common/modules/notification/services/notifications.service.ts b/src/common/modules/notification/services/notifications.service.ts new file mode 100644 index 0000000..fd39f4a --- /dev/null +++ b/src/common/modules/notification/services/notifications.service.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@nestjs/common'; +import { DeviceService } from '~/auth/services'; +import { PageOptionsRequestDto } from '~/core/dtos'; +import { Notification } from '../entities'; +import { NotificationsRepository } from '../repositories'; +import { FirebaseService } from './firebase.service'; + +@Injectable() +export class NotificationsService { + constructor( + private readonly deviceService: DeviceService, + private readonly firebaseService: FirebaseService, + private readonly notificationRepository: NotificationsRepository, + ) {} + + async sendPushNotification(userId: string, title: string, body: string) { + // Get the device tokens for the user + + const tokens = await this.deviceService.getTokens(userId); + + if (!tokens.length) { + return; + } + // Send the notification + return this.firebaseService.sendNotification(tokens, title, body); + } + + async getNotifications(userId: string, pageOptionsDto: PageOptionsRequestDto) { + const [[notifications, count], unreadCount] = await Promise.all([ + this.notificationRepository.getNotifications(userId, pageOptionsDto), + this.notificationRepository.getUnreadNotificationsCount(userId), + ]); + + return { notifications, count, unreadCount }; + } + + createNotification(notification: Partial) { + return this.notificationRepository.createNotification(notification); + } + + markAsRead(userId: string) { + return this.notificationRepository.markAsRead(userId); + } +} diff --git a/src/core/filters/all-exceptions.filter.ts b/src/core/filters/all-exceptions.filter.ts index 18ae9d9..57cb8dd 100644 --- a/src/core/filters/all-exceptions.filter.ts +++ b/src/core/filters/all-exceptions.filter.ts @@ -24,7 +24,6 @@ export class AllExceptionsFilter implements ExceptionFilter { const httpCtx: HttpArgumentsHost = host.switchToHttp(); const res = httpCtx.getResponse(); const i18n = new I18nContextWrapper(I18nContext.current()); - try { const status = this.extractStatusCode(exception); diff --git a/src/db/migrations/1734944692999-create-notification-entity-and-edit-device.ts b/src/db/migrations/1734944692999-create-notification-entity-and-edit-device.ts new file mode 100644 index 0000000..bb02bb6 --- /dev/null +++ b/src/db/migrations/1734944692999-create-notification-entity-and-edit-device.ts @@ -0,0 +1,32 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class CreateNotificationEntityAndEditDevice1734944692999 implements MigrationInterface { + name = 'CreateNotificationEntityAndEditDevice1734944692999'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "notifications" + ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), + "title" character varying NOT NULL, + "message" character varying NOT NULL, + "recipient" character varying, + "scope" character varying NOT NULL, + "status" character varying, + "channel" character varying NOT NULL, + "user_id" uuid, + "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), + CONSTRAINT "PK_6a72c3c0f683f6462415e653c3a" PRIMARY KEY ("id"))`, + ); + await queryRunner.query(`ALTER TABLE "devices" ADD "fcm_token" character varying`); + await queryRunner.query( + `ALTER TABLE "notifications" ADD CONSTRAINT "FK_9a8a82462cab47c73d25f49261f" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "notifications" DROP CONSTRAINT "FK_9a8a82462cab47c73d25f49261f"`); + await queryRunner.query(`ALTER TABLE "devices" DROP COLUMN "fcm_token"`); + await queryRunner.query(`DROP TABLE "notifications"`); + } +} diff --git a/src/db/migrations/index.ts b/src/db/migrations/index.ts index e3ee284..7946f1c 100644 --- a/src/db/migrations/index.ts +++ b/src/db/migrations/index.ts @@ -16,3 +16,4 @@ export * from './1734262619426-create-junior-registration-token-table'; export * from './1734503895302-create-money-request-entity'; export * from './1734601976591-create-allowance-entities'; export * from './1734861516657-create-gift-entities'; +export * from './1734944692999-create-notification-entity-and-edit-device'; diff --git a/src/saving-goals/repositories/saving-goals.repository.ts b/src/saving-goals/repositories/saving-goals.repository.ts index a4d7ca2..5a0c9c6 100644 --- a/src/saving-goals/repositories/saving-goals.repository.ts +++ b/src/saving-goals/repositories/saving-goals.repository.ts @@ -4,7 +4,6 @@ import { Repository } from 'typeorm'; import { PageOptionsRequestDto } from '~/core/dtos'; import { CreateGoalRequestDto } from '../dtos/request'; import { Category, SavingGoal } from '../entities'; -const ZERO = 0; const ONE = 1; @Injectable() export class SavingGoalsRepository { diff --git a/src/saving-goals/services/saving-goals.service.ts b/src/saving-goals/services/saving-goals.service.ts index dde53b6..6197dcb 100644 --- a/src/saving-goals/services/saving-goals.service.ts +++ b/src/saving-goals/services/saving-goals.service.ts @@ -7,7 +7,6 @@ import { SavingGoal } from '../entities'; import { IGoalStats } from '../interfaces'; import { SavingGoalsRepository } from '../repositories'; import { CategoryService } from './category.service'; -const ZERO = 0; @Injectable() export class SavingGoalsService { constructor(