diff --git a/.env.dist b/.env.dist index 2e4c53b..7ac2c10 100644 --- a/.env.dist +++ b/.env.dist @@ -22,9 +22,12 @@ DEPARTURE_MARGIN=900 # DEFAULT ROLE ROLE=passenger -# SEATS PROVIDED AS DRIVER / REQUESTED AS PASSENGER -SEATS_PROVIDED=3 +# SEATS PROPOSED AS DRIVER / REQUESTED AS PASSENGER +SEATS_PROPOSED=3 SEATS_REQUESTED=1 # ACCEPT ONLY SAME FREQUENCY REQUESTS STRICT_FREQUENCY=false + +# default timezone +DEFAULT_TIMEZONE=Europe/Paris diff --git a/README.md b/README.md index a8777f2..84f8a3a 100644 --- a/README.md +++ b/README.md @@ -48,30 +48,34 @@ npm run migrate The app exposes the following [gRPC](https://grpc.io/) services : -- **FindByUuid** : find an ad by its uuid +- **FindById** : find an ad by its id ```json { - "uuid": "80126a61-d128-4f96-afdb-92e33c75a3e1" + "id": "80126a61-d128-4f96-afdb-92e33c75a3e1" } ``` -- **Create** : create an ad (note that uuid is optional, a uuid will be automatically attributed if it is not provided) +- **Create** : create an ad (note that id is optional, an id (as a uuid) will be automatically attributed if it is not provided) Punctual driver ad : ```json { - "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245", + "userId": "80c9bb02-0931-4a1d-bea6-22d358992245", "driver": true, - "seatsDriver": 3, + "seatsProposed": 3, "frequency": "PUNCTUAL", - "departureDateTime": "2023-01-15 09:00", - "addresses": [ + "fromDate": "2023-01-15", + "toDate": "2023-01-15", + "schedule": { + "thu": "09:00" + }, + "waypoints": [ { "position": 0, - "lon": 48.68944505415954, - "lat": 6.176510296462267, + "lon": 48.689445, + "lat": 6.17651, "houseNumber": "5", "street": "Avenue Foch", "locality": "Nancy", @@ -94,18 +98,22 @@ The app exposes the following [gRPC](https://grpc.io/) services : ```json { - "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245", + "userId": "80c9bb02-0931-4a1d-bea6-22d358992245", "driver": true, "pasenger": true, - "seatsDriver": 3, - "seatsPassenger": 1, + "seatsProposed": 3, + "seatsRequested": 1, "frequency": "PUNCTUAL", - "departureDateTime": "2023-01-15 09:00", - "addresses": [ + "fromDate": "2023-01-15", + "toDate": "2023-01-15", + "schedule": { + "thu": "09:00" + }, + "waypoints": [ { "position": 0, - "lon": 48.68944505415954, - "lat": 6.176510296462267, + "lon": 48.689445, + "lat": 6.17651, "houseNumber": "5", "street": "Avenue Foch", "locality": "Nancy", @@ -128,7 +136,7 @@ The app exposes the following [gRPC](https://grpc.io/) services : ```json { - "userUuid": "80c9bb02-0931-4a1d-bea6-22d358992245", + "userId": "80c9bb02-0931-4a1d-bea6-22d358992245", "passenger": true, "seatsPassenger": 1, "frequency": "RECURRRENT", @@ -139,11 +147,11 @@ The app exposes the following [gRPC](https://grpc.io/) services : "tue": "07:05", "fri": "07:10" }, - "addresses": [ + "waypoints": [ { "position": 0, - "lon": 48.68944505415954, - "lat": 6.176510296462267, + "lon": 48.689445, + "lat": 6.17651, "houseNumber": "5", "street": "Avenue Foch", "locality": "Nancy", @@ -164,15 +172,14 @@ The app exposes the following [gRPC](https://grpc.io/) services : The list of possible options when creating an ad : - - uuid (optional): the uuid of the ad - - userUuid: the user uuid + - id (optional): the id of the ad (as a uuid) + - userId: the user id (as a uuid) - driver (boolean, optional): if the ad is a driver ad - passenger (boolean, optional): if the ad is a passenger ad - frequency: `PUNCTUAL` or `RECURRENT` - - departureDateTime (required if punctual): departure date and hour/minute for a punctual ad - - fromDate (required if recurrent): start date for recurrent ad - - toDate (required if recurrent): end date for recurrent ad - - schedule (required if recurrent): an object with the departure time for each carpooled day in the week + - fromDate: start date for recurrent ad, carpool date for punctual ad + - toDate: end date for recurrent ad, same as fromDate for punctual ad + - schedule: an object with the departure time for each carpooled day in the week (only the carpooled day for punctual ad) - marginDurations (optional): an object with the margin duration (in seconds) for each carpooled day in the week, eg: { @@ -181,10 +188,10 @@ The app exposes the following [gRPC](https://grpc.io/) services : "fri": 950 } - - seatsDriver (optional): number of seats proposed as driver; - - seatsPassenger (optional): number of seats requested as passenger; + - seatsProposed (optional): number of seats proposed as driver + - seatsRequested (optional): number of seats requested as passenger - strict (boolean, optional): if set to true, allow matching only with similar frequency ads - - addresses: an array of adresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads) + - waypoints: an array of addresses that represent the waypoints of the journey (only first and last waypoints are used for passenger ads). Note that positions are **required** and **must** be consecutives Default values must be set in `.env` file. diff --git a/package-lock.json b/package-lock.json index ccca153..645bdc0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,14 @@ { "name": "@mobicoop/ad", - "version": "0.0.1", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@mobicoop/ad", - "version": "0.0.1", + "version": "1.0.0", "license": "AGPL", "dependencies": { - "@automapper/classes": "^8.7.7", - "@automapper/core": "^8.7.7", - "@automapper/nestjs": "^8.7.7", "@grpc/grpc-js": "^1.8.14", "@grpc/proto-loader": "^0.7.6", "@liaoliaots/nestjs-redis": "^9.0.5", @@ -21,15 +18,19 @@ "@nestjs/config": "^2.3.1", "@nestjs/core": "^9.0.0", "@nestjs/cqrs": "^9.0.3", + "@nestjs/event-emitter": "^1.4.2", "@nestjs/microservices": "^9.4.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/terminus": "^9.2.2", "@prisma/client": "^4.13.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "geo-tz": "^7.0.7", "ioredis": "^5.3.2", + "nestjs-request-context": "^2.1.0", "reflect-metadata": "^0.1.13", - "rxjs": "^7.2.0" + "rxjs": "^7.2.0", + "timezonecomplete": "^5.12.4" }, "devDependencies": { "@nestjs/cli": "^9.0.0", @@ -39,6 +40,7 @@ "@types/jest": "29.5.0", "@types/node": "18.15.11", "@types/supertest": "^2.0.11", + "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "dotenv-cli": "^7.2.1", @@ -57,10 +59,17 @@ "typescript": "^4.7.4" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@acuminous/bitsyntax": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@acuminous/bitsyntax/-/bitsyntax-0.1.2.tgz", - "integrity": "sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==", + "license": "MIT", "dependencies": { "buffer-more-ints": "~1.0.0", "debug": "^4.3.4", @@ -72,9 +81,8 @@ }, "node_modules/@ampproject/remapping": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", - "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -85,9 +93,8 @@ }, "node_modules/@angular-devkit/core": { "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-16.0.1.tgz", - "integrity": "sha512-2uz98IqkKJlgnHbWQ7VeL4pb+snGAZXIama2KXi+k9GsRntdcw+udX8rL3G9SdUGUF+m6+147Y1oRBMHsO/v4w==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "8.12.0", "ajv-formats": "2.1.1", @@ -111,9 +118,8 @@ }, "node_modules/@angular-devkit/schematics": { "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-16.0.1.tgz", - "integrity": "sha512-A9D0LTYmiqiBa90GKcSuWb7hUouGIbm/AHbJbjL85WLLRbQA2PwKl7P5Mpd6nS/ZC0kfG4VQY3VOaDvb3qpI9g==", "dev": true, + "license": "MIT", "dependencies": { "@angular-devkit/core": "16.0.1", "jsonc-parser": "3.2.0", @@ -129,9 +135,8 @@ }, "node_modules/@angular-devkit/schematics-cli": { "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-16.0.1.tgz", - "integrity": "sha512-6KLA125dpgd6oJGtiO2JpZAb92uOG3njQGIt7NFcuQGW/5GO7J41vMXH9cBAfdtbV8SIggSmR/cIEE9ijfj6YQ==", "dev": true, + "license": "MIT", "dependencies": { "@angular-devkit/core": "16.0.1", "@angular-devkit/schematics": "16.0.1", @@ -151,9 +156,8 @@ }, "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { "version": "8.2.4", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz", - "integrity": "sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -175,44 +179,10 @@ "node": ">=12.0.0" } }, - "node_modules/@automapper/classes": { - "version": "8.7.7", - "resolved": "https://registry.npmjs.org/@automapper/classes/-/classes-8.7.7.tgz", - "integrity": "sha512-FSbvt6QE8XnhKKQZA3kpKLuLrr9x1iW+lNYTrawVLjxQ05zsCGccLxe7moMNrg1wFAVAouQKupFgCGQ7XRjmJw==", - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@automapper/core": "8.7.7", - "reflect-metadata": "~0.1.13" - } - }, - "node_modules/@automapper/core": { - "version": "8.7.7", - "resolved": "https://registry.npmjs.org/@automapper/core/-/core-8.7.7.tgz", - "integrity": "sha512-YfpDJ/xqwUuC0S+BLNk81ZJfeL7CmjirUX/Gk9eQyx146DKvneBZgeZ9v5rDB51Ti14jTxVHis+5JuT7W/q0TA==", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@automapper/nestjs": { - "version": "8.7.7", - "resolved": "https://registry.npmjs.org/@automapper/nestjs/-/nestjs-8.7.7.tgz", - "integrity": "sha512-9/uYY2cmN7SJjr2QxnfyXsteHrn/RHD+Dg0VMBflzK/e8Bh/KWyOve7+kaFixlUoyHe44aXs2LVaCslqt8wnhQ==", - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@automapper/core": "8.7.7", - "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", - "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0" - } - }, "node_modules/@babel/code-frame": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/highlight": "^7.22.5" }, @@ -222,18 +192,16 @@ }, "node_modules/@babel/compat-data": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.5.tgz", - "integrity": "sha512-4Jc/YuIaYqKnDDz892kPIledykKg12Aw1PYX5i/TY28anJtacvM1Rrr8wbieB9GfEJwlzqT0hUEao0CxEebiDA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.5.tgz", - "integrity": "sha512-SBuTAjg91A3eKOvD+bPEz3LlhHZRNu1nFOVts9lzDJTXshHTjII0BAtDS3Y2DAkdZdDKWVZGVwkDfc4Clxn1dg==", "dev": true, + "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.5", @@ -261,24 +229,21 @@ }, "node_modules/@babel/core/node_modules/convert-source-map": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz", - "integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5", "@jridgewell/gen-mapping": "^0.3.2", @@ -291,9 +256,8 @@ }, "node_modules/@babel/helper-compilation-targets": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.5.tgz", - "integrity": "sha512-Ji+ywpHeuqxB8WDxraCiqR0xfhYjiDE/e6k7FuIaANnoOFxAHskHChz4vA1mJC9Lbm01s1PVAGhQY4FUKSkGZw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.5", "@babel/helper-validator-option": "^7.22.5", @@ -310,27 +274,24 @@ }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/helper-environment-visitor": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.22.5", "@babel/types": "^7.22.5" @@ -341,9 +302,8 @@ }, "node_modules/@babel/helper-hoist-variables": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -353,9 +313,8 @@ }, "node_modules/@babel/helper-module-imports": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", - "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -365,9 +324,8 @@ }, "node_modules/@babel/helper-module-transforms": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz", - "integrity": "sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", "@babel/helper-module-imports": "^7.22.5", @@ -383,19 +341,17 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", - "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "version": "7.21.5", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", - "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -405,9 +361,8 @@ }, "node_modules/@babel/helper-split-export-declaration": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz", - "integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.22.5" }, @@ -417,36 +372,32 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", - "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", - "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.5.tgz", - "integrity": "sha512-pSXRmfE1vzcUIDFQcSGA5Mr+GxBV9oiRKDuDxXvWQQBCh8HoIjs/2DlDB7H8smac1IVrB9/xdXj2N3Wol9Cr+Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.22.5", "@babel/traverse": "^7.22.5", @@ -458,9 +409,8 @@ }, "node_modules/@babel/highlight": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.22.5", "chalk": "^2.0.0", @@ -472,9 +422,8 @@ }, "node_modules/@babel/highlight/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -484,9 +433,8 @@ }, "node_modules/@babel/highlight/node_modules/chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -498,42 +446,37 @@ }, "node_modules/@babel/highlight/node_modules/color-convert": { "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } }, "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/@babel/highlight/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/highlight/node_modules/supports-color": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -543,9 +486,8 @@ }, "node_modules/@babel/parser": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz", - "integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==", "dev": true, + "license": "MIT", "bin": { "parser": "bin/babel-parser.js" }, @@ -555,9 +497,8 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -567,9 +508,8 @@ }, "node_modules/@babel/plugin-syntax-bigint": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -579,9 +519,8 @@ }, "node_modules/@babel/plugin-syntax-class-properties": { "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -591,9 +530,8 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -603,9 +541,8 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -614,12 +551,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", - "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "version": "7.21.4", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -630,9 +566,8 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -642,9 +577,8 @@ }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -654,9 +588,8 @@ }, "node_modules/@babel/plugin-syntax-numeric-separator": { "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -666,9 +599,8 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -678,9 +610,8 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -690,9 +621,8 @@ }, "node_modules/@babel/plugin-syntax-optional-chaining": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -702,9 +632,8 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -716,12 +645,11 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", - "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "version": "7.21.4", "dev": true, + "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.20.2" }, "engines": { "node": ">=6.9.0" @@ -732,9 +660,8 @@ }, "node_modules/@babel/template": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.5", "@babel/parser": "^7.22.5", @@ -746,9 +673,8 @@ }, "node_modules/@babel/traverse": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz", - "integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.5", "@babel/generator": "^7.22.5", @@ -767,18 +693,16 @@ }, "node_modules/@babel/traverse/node_modules/globals": { "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/@babel/types": { "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.5", @@ -790,15 +714,13 @@ }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@colors/colors": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.1.90" @@ -806,9 +728,8 @@ }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" }, @@ -818,9 +739,8 @@ }, "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -828,9 +748,8 @@ }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -843,18 +762,16 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -875,9 +792,8 @@ }, "node_modules/@eslint/eslintrc/node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -891,39 +807,34 @@ }, "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@eslint/js": { "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", - "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@golevelup/nestjs-common": { "version": "1.4.4", - "resolved": "https://registry.npmjs.org/@golevelup/nestjs-common/-/nestjs-common-1.4.4.tgz", - "integrity": "sha512-NTjtOhHTMuGwiR3lmBQKKaRr++mHQEsh8AxtaH+/EWOYKMK2Cv/8duaH9MQ0hI3TwnouyaA5IRxYR1ZCUyNXOQ==", + "license": "MIT", "dependencies": { "nanoid": "^3.2.0" } }, "node_modules/@golevelup/nestjs-discovery": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@golevelup/nestjs-discovery/-/nestjs-discovery-3.0.0.tgz", - "integrity": "sha512-ZvkXtobTKxXB1LJanP/l6Z/Fing88IMBr3uabQpU2IWjfsstjh02qYDSU2cfD6CSmNldX5ewW5Pd+SdK2lU8Sw==", + "license": "MIT", "dependencies": { "lodash": "^4.17.15" } }, "node_modules/@golevelup/nestjs-modules": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@golevelup/nestjs-modules/-/nestjs-modules-0.6.1.tgz", - "integrity": "sha512-E0STg8In8fhIivnGDJAA70+XLPHzK5bMTkCnif9FbZ8waTYDQ3T/QQL0h73k+CUFeznn1hmuEW14sNaE+8cd7w==", + "license": "MIT", "dependencies": { "lodash": "^4.17.21" }, @@ -934,8 +845,7 @@ }, "node_modules/@golevelup/nestjs-rabbitmq": { "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@golevelup/nestjs-rabbitmq/-/nestjs-rabbitmq-3.6.1.tgz", - "integrity": "sha512-yxm7nVNRHeXiWHIFARF1NmMfIFu3WeixWkCIQzYisnS797SX1LC/kW2kIYJQvNHtcBwaro2WGhblyzvvQUJxXA==", + "license": "MIT", "dependencies": { "@golevelup/nestjs-common": "^1.4.4", "@golevelup/nestjs-discovery": "^3.0.0", @@ -946,8 +856,7 @@ }, "node_modules/@golevelup/nestjs-rabbitmq/node_modules/amqplib": { "version": "0.8.0", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.8.0.tgz", - "integrity": "sha512-icU+a4kkq4Y1PS4NNi+YPDMwdlbFcZ1EZTQT2nigW3fvOb6AOgUQ9+Mk4ue0Zu5cBg/XpDzB40oH10ysrk2dmA==", + "license": "MIT", "dependencies": { "bitsyntax": "~0.1.0", "bluebird": "^3.7.2", @@ -962,8 +871,6 @@ }, "node_modules/@golevelup/nestjs-rabbitmq/node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -977,12 +884,12 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/@grpc/grpc-js": { "version": "1.8.15", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.15.tgz", - "integrity": "sha512-H2Bu/w6+oQ58DsRbQol66ERBk3V5ZIak/z/MDx0T4EgDnJWps807I6BvTjq0v6UvZtOcLO+ur+Q9wvniqu3OJA==", + "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.7.0", "@types/node": ">=12.12.47" @@ -993,8 +900,7 @@ }, "node_modules/@grpc/proto-loader": { "version": "0.7.7", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.7.tgz", - "integrity": "sha512-1TIeXOi8TuSCQprPItwoMymZXxWT0CPxUhkrkeCUH+D8U7QDwQ6b7SUz2MaLuWM2llT+J/TVFLmQI5KtML3BhQ==", + "license": "Apache-2.0", "dependencies": { "@types/long": "^4.0.1", "lodash.camelcase": "^4.3.0", @@ -1011,9 +917,8 @@ }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -1025,9 +930,8 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -1038,20 +942,17 @@ }, "node_modules/@humanwhocodes/object-schema": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@ioredis/commands": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", - "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + "license": "MIT" }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, + "license": "ISC", "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -1065,27 +966,24 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -1096,9 +994,8 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1109,9 +1006,8 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -1121,9 +1017,8 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -1136,9 +1031,8 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -1148,27 +1042,24 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@jest/console": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", - "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", "@types/node": "*", @@ -1183,9 +1074,8 @@ }, "node_modules/@jest/core": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", - "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^29.5.0", "@jest/reporters": "^29.5.0", @@ -1230,9 +1120,8 @@ }, "node_modules/@jest/environment": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", - "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/fake-timers": "^29.5.0", "@jest/types": "^29.5.0", @@ -1245,9 +1134,8 @@ }, "node_modules/@jest/expect": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", "dev": true, + "license": "MIT", "dependencies": { "expect": "^29.5.0", "jest-snapshot": "^29.5.0" @@ -1258,9 +1146,8 @@ }, "node_modules/@jest/expect-utils": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", - "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", "dev": true, + "license": "MIT", "dependencies": { "jest-get-type": "^29.4.3" }, @@ -1270,9 +1157,8 @@ }, "node_modules/@jest/fake-timers": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", - "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", "@sinonjs/fake-timers": "^10.0.2", @@ -1287,9 +1173,8 @@ }, "node_modules/@jest/globals": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", - "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.5.0", "@jest/expect": "^29.5.0", @@ -1302,9 +1187,8 @@ }, "node_modules/@jest/reporters": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", - "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", "dev": true, + "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.5.0", @@ -1345,9 +1229,8 @@ }, "node_modules/@jest/schemas": { "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", - "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", "dev": true, + "license": "MIT", "dependencies": { "@sinclair/typebox": "^0.25.16" }, @@ -1357,9 +1240,8 @@ }, "node_modules/@jest/source-map": { "version": "29.4.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", - "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.15", "callsites": "^3.0.0", @@ -1371,9 +1253,8 @@ }, "node_modules/@jest/test-result": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", - "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^29.5.0", "@jest/types": "^29.5.0", @@ -1386,9 +1267,8 @@ }, "node_modules/@jest/test-sequencer": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", - "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "^29.5.0", "graceful-fs": "^4.2.9", @@ -1401,9 +1281,8 @@ }, "node_modules/@jest/transform": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", - "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.5.0", @@ -1427,9 +1306,8 @@ }, "node_modules/@jest/types": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", - "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^29.4.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -1444,9 +1322,8 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.0.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -1458,27 +1335,24 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/source-map": { "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.3.tgz", - "integrity": "sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -1486,15 +1360,13 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "3.1.0", "@jridgewell/sourcemap-codec": "1.4.14" @@ -1502,14 +1374,12 @@ }, "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@liaoliaots/nestjs-redis": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/@liaoliaots/nestjs-redis/-/nestjs-redis-9.0.5.tgz", - "integrity": "sha512-nPcGLj0zW4mEsYtQYfWx3o7PmrMjuzFk6+t/g2IRopAeWWUZZ/5nIJ4KTKiz/3DJEUkbX8PZqB+dOhklGF0SVA==", + "license": "MIT", "dependencies": { "tslib": "2.4.1" }, @@ -1524,16 +1394,14 @@ }, "node_modules/@lukeed/csprng": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", - "integrity": "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@mobicoop/configuration-module": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@mobicoop/configuration-module/-/configuration-module-1.2.0.tgz", - "integrity": "sha512-l0iDae7SgVVmjnCa2MBqAr3Er0yn4E7yiG8e7cs4XtNGUKrC1N0Ju56TEAraEYK9aZAZ36TCs06m1fep+rgwFA==", + "license": "AGPL", "dependencies": { "@golevelup/nestjs-rabbitmq": "^3.6.0", "@liaoliaots/nestjs-redis": "^9.0.5", @@ -1549,8 +1417,7 @@ }, "node_modules/@mobicoop/message-broker-module": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@mobicoop/message-broker-module/-/message-broker-module-1.2.0.tgz", - "integrity": "sha512-RoSHHK1GyQ/QVDmm3JS/wBfh171oChvyEp6YWmJd12krFLrPVn9MoEvZdyT3I5J31oBiUabMPle5Kdpw+Nrmww==", + "license": "AGPL", "dependencies": { "@golevelup/nestjs-rabbitmq": "^3.6.0", "@types/amqplib": "^0.10.1", @@ -1562,9 +1429,8 @@ }, "node_modules/@nestjs/cli": { "version": "9.5.0", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-9.5.0.tgz", - "integrity": "sha512-Z7q+3vNsQSG2d2r2Hl/OOj5EpfjVx3OfnJ9+KuAsOdw1sKLm7+Zc6KbhMFTd/eIvfx82ww3Nk72xdmfPYCulWA==", "dev": true, + "license": "MIT", "dependencies": { "@angular-devkit/core": "16.0.1", "@angular-devkit/schematics": "16.0.1", @@ -1598,9 +1464,8 @@ }, "node_modules/@nestjs/cli/node_modules/webpack": { "version": "5.82.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.82.1.tgz", - "integrity": "sha512-C6uiGQJ+Gt4RyHXXYt+v9f+SN1v83x68URwgxNQ98cvH8kxiuywWGP4XeNZ1paOzZ63aY3cTciCEQJNFUljlLw==", "dev": true, + "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.0", @@ -1644,12 +1509,11 @@ } }, "node_modules/@nestjs/common": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.4.3.tgz", - "integrity": "sha512-Gd6D4IaYj01o14Bwv81ukidn4w3bPHCblMUq+SmUmWLyosK+XQmInCS09SbDDZyL8jy86PngtBLTdhJ2bXSUig==", + "version": "9.4.2", + "license": "MIT", "dependencies": { "iterare": "1.2.1", - "tslib": "2.5.3", + "tslib": "2.5.2", "uid": "2.0.2" }, "funding": { @@ -1676,16 +1540,14 @@ } }, "node_modules/@nestjs/common/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + "version": "2.5.2", + "license": "0BSD" }, "node_modules/@nestjs/config": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.3.4.tgz", - "integrity": "sha512-IGdSF+0F9MJO6dCRTEahdxPz4iVijjtolcFBxnY+2QYM3bXYQvAgzskGZi+WkAFJN/VzR3TEp60gN5sI74GxPA==", + "version": "2.3.2", + "license": "MIT", "dependencies": { - "dotenv": "16.1.4", + "dotenv": "16.0.3", "dotenv-expand": "10.0.0", "lodash": "4.17.21", "uuid": "9.0.0" @@ -1697,16 +1559,15 @@ } }, "node_modules/@nestjs/core": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-9.4.3.tgz", - "integrity": "sha512-Qi63+wi55Jh4sDyaj5Hhx2jOpKqT386aeo+VOKsxnd+Ql9VvkO/FjmuwBGUyzkJt29ENYc+P0Sx/k5LtstNpPQ==", + "version": "9.4.2", "hasInstallScript": true, + "license": "MIT", "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", "path-to-regexp": "3.2.0", - "tslib": "2.5.3", + "tslib": "2.5.2", "uid": "2.0.2" }, "funding": { @@ -1734,14 +1595,12 @@ } }, "node_modules/@nestjs/core/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + "version": "2.5.2", + "license": "0BSD" }, "node_modules/@nestjs/cqrs": { "version": "9.0.4", - "resolved": "https://registry.npmjs.org/@nestjs/cqrs/-/cqrs-9.0.4.tgz", - "integrity": "sha512-nWDF+xs4jqs6OjxFg/wVSd0NiIV9+EFCJrJNTo4VRWe78CcAaitbp56CBspUh4gKyfkci95i+EhHdEqRXKFptg==", + "license": "MIT", "dependencies": { "uuid": "9.0.0" }, @@ -1752,13 +1611,24 @@ "rxjs": "^7.2.0" } }, + "node_modules/@nestjs/event-emitter": { + "version": "1.4.2", + "license": "MIT", + "dependencies": { + "eventemitter2": "6.4.9" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0", + "@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0", + "reflect-metadata": "^0.1.12" + } + }, "node_modules/@nestjs/microservices": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/@nestjs/microservices/-/microservices-9.4.3.tgz", - "integrity": "sha512-piMw8d3C4ppc5St5AhQEtecMhyeBK2Q1VYk4AL3NKtG6U0fzz/6KLiETpWdKXmazeI/m7qac2upOvwmRzle0aA==", + "version": "9.4.2", + "license": "MIT", "dependencies": { "iterare": "1.2.1", - "tslib": "2.5.3" + "tslib": "2.5.2" }, "funding": { "type": "opencollective", @@ -1810,20 +1680,18 @@ } }, "node_modules/@nestjs/microservices/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + "version": "2.5.2", + "license": "0BSD" }, "node_modules/@nestjs/platform-express": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.4.3.tgz", - "integrity": "sha512-FpdczWoRSC0zz2dNL9u2AQLXKXRVtq4HgHklAhbL59X0uy+mcxhlSThG7DHzDMkoSnuuHY8ojDVf7mDxk+GtCw==", + "version": "9.4.2", + "license": "MIT", "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", "express": "4.18.2", "multer": "1.4.4-lts.1", - "tslib": "2.5.3" + "tslib": "2.5.2" }, "funding": { "type": "opencollective", @@ -1835,15 +1703,13 @@ } }, "node_modules/@nestjs/platform-express/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==" + "version": "2.5.2", + "license": "0BSD" }, "node_modules/@nestjs/schematics": { "version": "9.2.0", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-9.2.0.tgz", - "integrity": "sha512-wHpNJDPzM6XtZUOB3gW0J6mkFCSJilzCM3XrHI1o0C8vZmFE1snbmkIXNyoi1eV0Nxh1BMymcgz5vIMJgQtTqw==", "dev": true, + "license": "MIT", "dependencies": { "@angular-devkit/core": "16.0.1", "@angular-devkit/schematics": "16.0.1", @@ -1856,8 +1722,7 @@ }, "node_modules/@nestjs/terminus": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/@nestjs/terminus/-/terminus-9.2.2.tgz", - "integrity": "sha512-AWUA8XLcgxWUjUFYHDqi42M7CZn2e+DEWxP+MqNAbMzz4ybB5jGcFK5Fy8qwaNBoWg6KMF1JiXOOygGXgk9ydg==", + "license": "MIT", "dependencies": { "boxen": "5.1.2", "check-disk-space": "3.3.1" @@ -1920,12 +1785,11 @@ } }, "node_modules/@nestjs/testing": { - "version": "9.4.3", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-9.4.3.tgz", - "integrity": "sha512-LDT8Ai2eKnTzvnPaJwWOK03qTaFap5uHHsJCv6dL0uKWk6hyF9jms8DjyVaGsaujCaXDG8izl1mDEER0OmxaZA==", + "version": "9.4.2", "dev": true, + "license": "MIT", "dependencies": { - "tslib": "2.5.3" + "tslib": "2.5.2" }, "funding": { "type": "opencollective", @@ -1947,16 +1811,14 @@ } }, "node_modules/@nestjs/testing/node_modules/tslib": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.3.tgz", - "integrity": "sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==", - "dev": true + "version": "2.5.2", + "dev": true, + "license": "0BSD" }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1967,18 +1829,16 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1989,8 +1849,7 @@ }, "node_modules/@nuxtjs/opencollective": { "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", - "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "consola": "^2.15.0", @@ -2006,9 +1865,8 @@ }, "node_modules/@prisma/client": { "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-4.15.0.tgz", - "integrity": "sha512-xnROvyABcGiwqRNdrObHVZkD9EjkJYHOmVdlKy1yGgI+XOzvMzJ4tRg3dz1pUlsyhKxXGCnjIQjWW+2ur+YXuw==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@prisma/engines-version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944" }, @@ -2026,40 +1884,33 @@ }, "node_modules/@prisma/engines": { "version": "4.15.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-4.15.0.tgz", - "integrity": "sha512-FTaOCGs0LL0OW68juZlGxFtYviZa4xdQj/rQEdat2txw0s3Vu/saAPKjNVXfIgUsGXmQ72HPgNr6935/P8FNAA==", "devOptional": true, - "hasInstallScript": true + "hasInstallScript": true, + "license": "Apache-2.0" }, "node_modules/@prisma/engines-version": { "version": "4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-4.15.0-28.8fbc245156db7124f997f4cecdd8d1219e360944.tgz", - "integrity": "sha512-sVOig4tjGxxlYaFcXgE71f/rtFhzyYrfyfNFUsxCIEJyVKU9rdOWIlIwQ2NQ7PntvGnn+x0XuFo4OC1jvPJKzg==" + "license": "Apache-2.0" }, "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" }, "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" }, "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" }, "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" }, "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", "dependencies": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -2067,90 +1918,104 @@ }, "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" }, "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" }, "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" }, "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" }, "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" }, "node_modules/@sinclair/typebox": { "version": "0.25.24", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", - "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", - "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "type-detect": "4.0.8" } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.1.0.tgz", - "integrity": "sha512-w1qd368vtrwttm1PRJWPW1QHlbmHrVDGs1eBH/jZvRPUFS4MNXV9Q33EQdjOdeAxZ7O8+3wM7zxztm2nfUSyKw==", + "version": "10.2.0", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "node_modules/@tsconfig/node10": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/@turf/boolean-point-in-polygon": { + "version": "6.5.0", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0", + "@turf/invariant": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/helpers": { + "version": "6.5.0", + "license": "MIT", + "funding": { + "url": "https://opencollective.com/turf" + } + }, + "node_modules/@turf/invariant": { + "version": "6.5.0", + "license": "MIT", + "dependencies": { + "@turf/helpers": "^6.5.0" + }, + "funding": { + "url": "https://opencollective.com/turf" + } }, "node_modules/@types/amqplib": { "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@types/amqplib/-/amqplib-0.10.1.tgz", - "integrity": "sha512-j6ANKT79ncUDnAs/+9r9eDujxbeJoTjoVu33gHHcaPfmLQaMhvfbH2GqSe8KUM444epAp1Vl3peVOQfZk3UIqA==", + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/babel__core": { "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", - "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -2161,18 +2026,16 @@ }, "node_modules/@types/babel__generator": { "version": "7.6.4", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", - "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" } }, "node_modules/@types/babel__template": { "version": "7.4.1", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", - "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -2180,18 +2043,16 @@ }, "node_modules/@types/babel__traverse": { "version": "7.20.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", - "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/types": "^7.20.7" } }, "node_modules/@types/body-parser": { "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", "dev": true, + "license": "MIT", "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -2199,24 +2060,21 @@ }, "node_modules/@types/connect": { "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/cookiejar": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz", - "integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/eslint": { - "version": "8.40.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.40.2.tgz", - "integrity": "sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==", + "version": "8.40.0", "dev": true, + "license": "MIT", "dependencies": { "@types/estree": "*", "@types/json-schema": "*" @@ -2224,9 +2082,8 @@ }, "node_modules/@types/eslint-scope": { "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", "dev": true, + "license": "MIT", "dependencies": { "@types/eslint": "*", "@types/estree": "*" @@ -2234,15 +2091,13 @@ }, "node_modules/@types/estree": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/express": { "version": "4.17.17", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", - "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -2252,9 +2107,8 @@ }, "node_modules/@types/express-serve-static-core": { "version": "4.17.35", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", - "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -2264,42 +2118,37 @@ }, "node_modules/@types/graceful-fs": { "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/jest": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-3Emr5VOl/aoBwnWcH/EFQvlSAmjV+XtV9GGu5mwdYew5vhQh0IUZx/60x0TzHDu09Bi7HMx10t/namdJw5QIcg==", "dev": true, + "license": "MIT", "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" @@ -2307,61 +2156,51 @@ }, "node_modules/@types/json-schema": { "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", - "dev": true + "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" }, "node_modules/@types/mime": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "18.15.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz", - "integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q==" + "license": "MIT" }, "node_modules/@types/parse-json": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/prettier": { "version": "2.7.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", - "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/qs": { "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/semver": { "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/send": { "version": "0.17.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", - "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -2369,9 +2208,8 @@ }, "node_modules/@types/serve-static": { "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.1.tgz", - "integrity": "sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/mime": "*", "@types/node": "*" @@ -2379,15 +2217,13 @@ }, "node_modules/@types/stack-utils": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/superagent": { "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-4.1.18.tgz", - "integrity": "sha512-LOWgpacIV8GHhrsQU+QMZuomfqXiqzz3ILLkCtKx3Us6AmomFViuzKT9D693QTKgyut2oCytMG8/efOop+DB+w==", "dev": true, + "license": "MIT", "dependencies": { "@types/cookiejar": "*", "@types/node": "*" @@ -2395,43 +2231,43 @@ }, "node_modules/@types/supertest": { "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-2.0.12.tgz", - "integrity": "sha512-X3HPWTwXRerBZS7Mo1k6vMVR1Z6zmJcDVn5O/31whe0tnjE4te6ZJSJGq1RiqHPjzPdMTfjCFogDJmwng9xHaQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/superagent": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.2", + "dev": true, + "license": "MIT" + }, "node_modules/@types/validator": { "version": "13.7.17", - "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.17.tgz", - "integrity": "sha512-aqayTNmeWrZcvnG2MG9eGYI6b7S5fl+yKgPs6bAjOTwPS316R5SxBGKvtSExfyoJU7pIeHJfsHI0Ji41RVMkvQ==" + "license": "MIT" }, "node_modules/@types/yargs": { "version": "17.0.24", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", - "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.11.tgz", - "integrity": "sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==", + "version": "5.59.9", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/type-utils": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/scope-manager": "5.59.9", + "@typescript-eslint/type-utils": "5.59.9", + "@typescript-eslint/utils": "5.59.9", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", @@ -2457,14 +2293,13 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.59.11.tgz", - "integrity": "sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==", + "version": "5.59.9", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.59.9", + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/typescript-estree": "5.59.9", "debug": "^4.3.4" }, "engines": { @@ -2484,13 +2319,12 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.11.tgz", - "integrity": "sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==", + "version": "5.59.9", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11" + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/visitor-keys": "5.59.9" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2501,13 +2335,12 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.11.tgz", - "integrity": "sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==", + "version": "5.59.9", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "5.59.11", - "@typescript-eslint/utils": "5.59.11", + "@typescript-eslint/typescript-estree": "5.59.9", + "@typescript-eslint/utils": "5.59.9", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -2528,10 +2361,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.11.tgz", - "integrity": "sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==", + "version": "5.59.9", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -2541,13 +2373,12 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.11.tgz", - "integrity": "sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==", + "version": "5.59.9", "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/visitor-keys": "5.59.11", + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/visitor-keys": "5.59.9", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2568,17 +2399,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.11.tgz", - "integrity": "sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==", + "version": "5.59.9", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.59.11", - "@typescript-eslint/types": "5.59.11", - "@typescript-eslint/typescript-estree": "5.59.11", + "@typescript-eslint/scope-manager": "5.59.9", + "@typescript-eslint/types": "5.59.9", + "@typescript-eslint/typescript-estree": "5.59.9", "eslint-scope": "^5.1.1", "semver": "^7.3.7" }, @@ -2594,12 +2424,11 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.59.11", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.11.tgz", - "integrity": "sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==", + "version": "5.59.9", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "5.59.11", + "@typescript-eslint/types": "5.59.9", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -2612,9 +2441,8 @@ }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", - "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6" @@ -2622,27 +2450,23 @@ }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", - "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -2651,15 +2475,13 @@ }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", - "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -2669,33 +2491,29 @@ }, "node_modules/@webassemblyjs/ieee754": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, + "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "node_modules/@webassemblyjs/leb128": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" } }, "node_modules/@webassemblyjs/utf8": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", - "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -2709,9 +2527,8 @@ }, "node_modules/@webassemblyjs/wasm-gen": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", - "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-wasm-bytecode": "1.11.6", @@ -2722,9 +2539,8 @@ }, "node_modules/@webassemblyjs/wasm-opt": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", - "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-buffer": "1.11.6", @@ -2734,9 +2550,8 @@ }, "node_modules/@webassemblyjs/wasm-parser": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", - "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", "@webassemblyjs/helper-api-error": "1.11.6", @@ -2748,9 +2563,8 @@ }, "node_modules/@webassemblyjs/wast-printer": { "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", - "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", "dev": true, + "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", "@xtuc/long": "4.2.2" @@ -2758,20 +2572,17 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/accepts": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" @@ -2782,9 +2593,8 @@ }, "node_modules/acorn": { "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2794,36 +2604,32 @@ }, "node_modules/acorn-import-assertions": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^8" } }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/acorn-walk": { "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, "node_modules/ajv": { "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -2837,9 +2643,8 @@ }, "node_modules/ajv-formats": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^8.0.0" }, @@ -2854,8 +2659,7 @@ }, "node_modules/amqp-connection-manager": { "version": "3.9.0", - "resolved": "https://registry.npmjs.org/amqp-connection-manager/-/amqp-connection-manager-3.9.0.tgz", - "integrity": "sha512-ZKw9ckJKz40Lc2pC7DY0NVocpzPalMaCgv0sBn+N4er2QFAJul9pIiMOm/FsPHeCzB+FulV7PckOpmZvWvewGQ==", + "license": "MIT", "dependencies": { "promise-breaker": "^5.0.0" }, @@ -2869,8 +2673,7 @@ }, "node_modules/amqplib": { "version": "0.10.3", - "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.10.3.tgz", - "integrity": "sha512-UHmuSa7n8vVW/a5HGh2nFPqAEr8+cD4dEZ6u9GjP91nHfr1a54RyAKyra7Sb5NH7NBKOUlyQSMXIp0qAixKexw==", + "license": "MIT", "dependencies": { "@acuminous/bitsyntax": "^0.1.2", "buffer-more-ints": "~1.0.0", @@ -2883,26 +2686,23 @@ }, "node_modules/ansi-align": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", "dependencies": { "string-width": "^4.1.0" } }, "node_modules/ansi-colors": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/ansi-escapes": { "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.21.3" }, @@ -2915,9 +2715,8 @@ }, "node_modules/ansi-escapes/node_modules/type-fest": { "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -2927,16 +2726,14 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -2949,9 +2746,8 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2962,52 +2758,48 @@ }, "node_modules/append-field": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + "license": "MIT" }, "node_modules/arg": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "license": "MIT" + }, + "node_modules/array-source": { + "version": "0.0.4", + "license": "BSD-3-Clause" }, "node_modules/array-union": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/asap": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/babel-jest": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", - "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", "dev": true, + "license": "MIT", "dependencies": { "@jest/transform": "^29.5.0", "@types/babel__core": "^7.1.14", @@ -3026,9 +2818,8 @@ }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", @@ -3042,9 +2833,8 @@ }, "node_modules/babel-plugin-jest-hoist": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", - "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", @@ -3057,9 +2847,8 @@ }, "node_modules/babel-preset-current-node-syntax": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", "dev": true, + "license": "MIT", "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -3080,9 +2869,8 @@ }, "node_modules/babel-preset-jest": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", - "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", "dev": true, + "license": "MIT", "dependencies": { "babel-plugin-jest-hoist": "^29.5.0", "babel-preset-current-node-syntax": "^1.0.0" @@ -3096,14 +2884,11 @@ }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ { @@ -3118,21 +2903,20 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/bitsyntax": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", - "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", + "license": "MIT", "dependencies": { "buffer-more-ints": "~1.0.0", "debug": "~2.6.9", @@ -3144,22 +2928,19 @@ }, "node_modules/bitsyntax/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/bitsyntax/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "license": "MIT" }, "node_modules/bl": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "license": "MIT", "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3168,9 +2949,8 @@ }, "node_modules/bl/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==", "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -3182,8 +2962,6 @@ }, "node_modules/bl/node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { @@ -3198,26 +2976,24 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/bl/node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" } }, "node_modules/bluebird": { "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -3239,21 +3015,18 @@ }, "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "license": "MIT" }, "node_modules/boxen": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", - "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "license": "MIT", "dependencies": { "ansi-align": "^3.0.0", "camelcase": "^6.2.0", @@ -3273,9 +3046,8 @@ }, "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, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3283,9 +3055,8 @@ }, "node_modules/braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.0.1" }, @@ -3294,9 +3065,7 @@ } }, "node_modules/browserslist": { - "version": "4.21.8", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.8.tgz", - "integrity": "sha512-j+7xYe+v+q2Id9qbBeCI8WX5NmZSRe8es1+0xntD/+gaWXznP8tFEkv5IgSaHf5dS1YwVMbX/4W6m937mj+wQw==", + "version": "4.21.7", "dev": true, "funding": [ { @@ -3312,9 +3081,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001502", - "electron-to-chromium": "^1.4.428", + "caniuse-lite": "^1.0.30001489", + "electron-to-chromium": "^1.4.411", "node-releases": "^2.0.12", "update-browserslist-db": "^1.0.11" }, @@ -3327,9 +3097,8 @@ }, "node_modules/bs-logger": { "version": "0.2.6", - "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", - "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", "dev": true, + "license": "MIT", "dependencies": { "fast-json-stable-stringify": "2.x" }, @@ -3339,17 +3108,14 @@ }, "node_modules/bser": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "node-int64": "^0.4.0" } }, "node_modules/buffer": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "funding": [ { @@ -3365,6 +3131,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3372,18 +3139,14 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + "license": "MIT" }, "node_modules/buffer-more-ints": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", - "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + "license": "MIT" }, "node_modules/busboy": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", "dependencies": { "streamsearch": "^1.1.0" }, @@ -3393,16 +3156,14 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/call-bind": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.1", "get-intrinsic": "^1.0.2" @@ -3413,17 +3174,15 @@ }, "node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/camelcase": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -3432,9 +3191,7 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001503", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001503.tgz", - "integrity": "sha512-Sf9NiF+wZxPfzv8Z3iS0rXM1Do+iOy2Lxvib38glFX+08TCYYYGR5fRJXk4d77C4AYwhUjgYgMsMudbh2TqCKw==", + "version": "1.0.30001495", "dev": true, "funding": [ { @@ -3449,12 +3206,12 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3468,31 +3225,26 @@ }, "node_modules/char-regex": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/chardet": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/check-disk-space": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/check-disk-space/-/check-disk-space-3.3.1.tgz", - "integrity": "sha512-iOrT8yCZjSnyNZ43476FE2rnssvgw5hnuwOM0hm8Nj1qa0v4ieUUEbCyxxsEliaoDUb/75yCOL71zkDiDBLbMQ==", + "license": "MIT", "engines": { "node": ">=12" } }, "node_modules/chokidar": { "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", "dev": true, "funding": [ { @@ -3500,6 +3252,7 @@ "url": "https://paulmillr.com/funding/" } ], + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -3518,17 +3271,14 @@ }, "node_modules/chrome-trace-event": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0" } }, "node_modules/ci-info": { "version": "3.8.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", - "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true, "funding": [ { @@ -3536,25 +3286,23 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/cjs-module-lexer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", - "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", - "dev": true + "version": "1.2.2", + "dev": true, + "license": "MIT" }, "node_modules/class-transformer": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + "license": "MIT" }, "node_modules/class-validator": { "version": "0.14.0", - "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.0.tgz", - "integrity": "sha512-ct3ltplN8I9fOwUd8GrP8UQixwff129BkEtuWDKL5W45cQuLd19xqmTLu5ge78YDm/fdje6FMt0hGOhl0lii3A==", + "license": "MIT", "dependencies": { "@types/validator": "^13.7.10", "libphonenumber-js": "^1.10.14", @@ -3563,8 +3311,7 @@ }, "node_modules/cli-boxes": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", - "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "license": "MIT", "engines": { "node": ">=6" }, @@ -3574,9 +3321,8 @@ }, "node_modules/cli-cursor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, + "license": "MIT", "dependencies": { "restore-cursor": "^3.1.0" }, @@ -3586,9 +3332,8 @@ }, "node_modules/cli-spinners": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", - "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -3598,9 +3343,8 @@ }, "node_modules/cli-table3": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", "dev": true, + "license": "MIT", "dependencies": { "string-width": "^4.2.0" }, @@ -3613,17 +3357,15 @@ }, "node_modules/cli-width": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", "dev": true, + "license": "ISC", "engines": { "node": ">= 10" } }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -3635,26 +3377,23 @@ }, "node_modules/clone": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8" } }, "node_modules/cluster-key-slot": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", - "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==", + "license": "Apache-2.0", "engines": { "node": ">=0.10.0" } }, "node_modules/co": { "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, + "license": "MIT", "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -3662,14 +3401,12 @@ }, "node_modules/collect-v8-coverage": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -3679,14 +3416,12 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3696,32 +3431,28 @@ }, "node_modules/commander": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/component-emitter": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/concat-stream": { "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "engines": [ "node >= 0.8" ], + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -3731,13 +3462,11 @@ }, "node_modules/concat-stream/node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "license": "MIT" }, "node_modules/concat-stream/node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -3750,21 +3479,18 @@ }, "node_modules/concat-stream/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } }, "node_modules/consola": { "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==" + "license": "MIT" }, "node_modules/content-disposition": { "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -3774,8 +3500,6 @@ }, "node_modules/content-disposition/node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -3789,50 +3513,44 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cookie": { "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "license": "MIT" }, "node_modules/cookiejar": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "license": "MIT" }, "node_modules/cors": { "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", "dependencies": { "object-assign": "^4", "vary": "^1" @@ -3843,9 +3561,8 @@ }, "node_modules/cosmiconfig": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", "dev": true, + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -3859,15 +3576,13 @@ }, "node_modules/create-require": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3879,8 +3594,7 @@ }, "node_modules/debug": { "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -3895,30 +3609,26 @@ }, "node_modules/dedent": { "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/defaults": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "license": "MIT", "dependencies": { "clone": "^1.0.2" }, @@ -3928,33 +3638,29 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } }, "node_modules/denque": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", "engines": { "node": ">=0.10" } }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -3962,18 +3668,16 @@ }, "node_modules/detect-newline": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/dezalgo": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, + "license": "ISC", "dependencies": { "asap": "^2.0.0", "wrappy": "1" @@ -3981,27 +3685,24 @@ }, "node_modules/diff": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, "node_modules/diff-sequences": { "version": "29.4.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", - "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/dir-glob": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -4011,9 +3712,8 @@ }, "node_modules/doctrine": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -4022,21 +3722,16 @@ } }, "node_modules/dotenv": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.1.4.tgz", - "integrity": "sha512-m55RtE8AsPeJBpOIFKihEmqUcoVncQIwo7x9U8ZwLEZw9ZpXboz2c+rvog+jUaJvVrZ5kBOeYQBX5+8Aa/OZQw==", + "version": "16.0.3", + "license": "BSD-2-Clause", "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/dotenv-cli": { "version": "7.2.1", - "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.2.1.tgz", - "integrity": "sha512-ODHbGTskqRtXAzZapDPvgNuDVQApu4oKX8lZW7Y0+9hKA6le1ZJlyRS687oU9FXjOVEDU/VFV6zI125HzhM1UQ==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "dotenv": "^16.0.0", @@ -4049,28 +3744,24 @@ }, "node_modules/dotenv-expand": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", - "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" } }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.4.430", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.430.tgz", - "integrity": "sha512-FytjTbGwz///F+ToZ5XSeXbbSaXalsVRXsz2mHityI5gfxft7ieW3HqFLkU5V1aIrY42aflICqbmFoDxW10etg==", - "dev": true + "version": "1.4.425", + "dev": true, + "license": "ISC" }, "node_modules/emittery": { "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -4080,31 +3771,27 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "license": "MIT" }, "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==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/end-of-stream": { "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, + "license": "MIT", "dependencies": { "once": "^1.4.0" } }, "node_modules/enhanced-resolve": { - "version": "5.15.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz", - "integrity": "sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==", + "version": "5.14.1", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" @@ -4115,37 +3802,32 @@ }, "node_modules/error-ex": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "dev": true, + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } }, "node_modules/es-module-lexer": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.3.0.tgz", - "integrity": "sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==", - "dev": true + "version": "1.2.1", + "dev": true, + "license": "MIT" }, "node_modules/escalade": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "license": "MIT" }, "node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -4155,9 +3837,8 @@ }, "node_modules/eslint": { "version": "8.42.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", - "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", @@ -4211,9 +3892,8 @@ }, "node_modules/eslint-config-prettier": { "version": "8.8.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", - "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -4223,9 +3903,8 @@ }, "node_modules/eslint-plugin-prettier": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", "dev": true, + "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0" }, @@ -4244,9 +3923,8 @@ }, "node_modules/eslint-scope": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -4257,9 +3935,8 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -4269,9 +3946,8 @@ }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -4285,9 +3961,8 @@ }, "node_modules/eslint/node_modules/eslint-scope": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -4301,18 +3976,16 @@ }, "node_modules/eslint/node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/eslint/node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.3" }, @@ -4322,15 +3995,13 @@ }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/espree": { "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", @@ -4345,9 +4016,8 @@ }, "node_modules/esprima": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -4358,9 +4028,8 @@ }, "node_modules/esquery": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -4370,18 +4039,16 @@ }, "node_modules/esquery/node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -4391,53 +4058,51 @@ }, "node_modules/esrecurse/node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/estraverse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, + "node_modules/eventemitter2": { + "version": "6.4.9", + "license": "MIT" + }, "node_modules/events": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.x" } }, "node_modules/execa": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -4458,8 +4123,6 @@ }, "node_modules/exit": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", "dev": true, "engines": { "node": ">= 0.8.0" @@ -4467,9 +4130,8 @@ }, "node_modules/expect": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", - "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", "dev": true, + "license": "MIT", "dependencies": { "@jest/expect-utils": "^29.5.0", "jest-get-type": "^29.4.3", @@ -4483,8 +4145,7 @@ }, "node_modules/express": { "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -4524,8 +4185,7 @@ }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", @@ -4547,26 +4207,22 @@ }, "node_modules/express/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/express/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "license": "MIT" }, "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==" + "license": "MIT" }, "node_modules/express/node_modules/raw-body": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -4579,8 +4235,6 @@ }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -4594,13 +4248,13 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/external-editor": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "dev": true, + "license": "MIT", "dependencies": { "chardet": "^0.7.0", "iconv-lite": "^0.4.24", @@ -4612,21 +4266,18 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-glob": { "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -4640,44 +4291,38 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==" + "license": "MIT" }, "node_modules/fastq": { "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } }, "node_modules/fb-watchman": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "bser": "2.1.1" } }, "node_modules/figures": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^1.0.5" }, @@ -4690,18 +4335,16 @@ }, "node_modules/figures/node_modules/escape-string-regexp": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } }, "node_modules/file-entry-cache": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -4709,11 +4352,17 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/file-source": { + "version": "0.6.1", + "license": "BSD-3-Clause", + "dependencies": { + "stream-source": "0.3" + } + }, "node_modules/fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -4723,8 +4372,7 @@ }, "node_modules/finalhandler": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", @@ -4740,22 +4388,19 @@ }, "node_modules/finalhandler/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -4769,9 +4414,8 @@ }, "node_modules/flat-cache": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.1.0", "rimraf": "^3.0.2" @@ -4782,9 +4426,8 @@ }, "node_modules/flat-cache/node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -4797,15 +4440,13 @@ }, "node_modules/flatted": { "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fork-ts-checker-webpack-plugin": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-8.0.0.tgz", - "integrity": "sha512-mX3qW3idpueT2klaQXBzrIM/pHw+T0B/V9KHEvNrqijTq9NFnMZU6oreVxDYcf33P8a5cW+67PjodNHthGnNVg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.16.7", "chalk": "^4.1.2", @@ -4831,9 +4472,8 @@ }, "node_modules/form-data": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -4845,9 +4485,8 @@ }, "node_modules/formidable": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.2.tgz", - "integrity": "sha512-CM3GuJ57US06mlpQ47YcunuUZ9jpm8Vx+P2CGt2j7HpgkKZO/DJYQ0Bobim8G6PFQmK5lOqOOdUXboU+h73A4g==", "dev": true, + "license": "MIT", "dependencies": { "dezalgo": "^1.0.4", "hexoid": "^1.0.0", @@ -4860,25 +4499,22 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/fs-extra": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -4890,56 +4526,113 @@ }, "node_modules/fs-monkey": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", - "dev": true + "dev": true, + "license": "Unlicense" }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } + "license": "ISC" }, "node_modules/function-bind": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "license": "MIT" }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } }, + "node_modules/geo-tz": { + "version": "7.0.7", + "license": "MIT", + "dependencies": { + "@turf/boolean-point-in-polygon": "^6.5.0", + "@turf/helpers": "^6.5.0", + "geobuf": "^3.0.2", + "pbf": "^3.2.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/geobuf": { + "version": "3.0.2", + "license": "ISC", + "dependencies": { + "concat-stream": "^2.0.0", + "pbf": "^3.2.1", + "shapefile": "~0.6.6" + }, + "bin": { + "geobuf2json": "bin/geobuf2json", + "json2geobuf": "bin/json2geobuf", + "shp2geobuf": "bin/shp2geobuf" + } + }, + "node_modules/geobuf/node_modules/concat-stream": { + "version": "2.0.0", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/geobuf/node_modules/readable-stream": { + "version": "3.6.2", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/geobuf/node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/geobuf/node_modules/string_decoder": { + "version": "1.3.0", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-intrinsic": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", - "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.1", "has": "^1.0.3", @@ -4952,18 +4645,16 @@ }, "node_modules/get-package-type": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0.0" } }, "node_modules/get-stream": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -4973,9 +4664,8 @@ }, "node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4993,9 +4683,8 @@ }, "node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -5005,15 +4694,13 @@ }, "node_modules/glob-to-regexp": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true + "dev": true, + "license": "BSD-2-Clause" }, "node_modules/globals": { "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -5026,9 +4713,8 @@ }, "node_modules/globby": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", "dev": true, + "license": "MIT", "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -5046,26 +4732,22 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/grapheme-splitter": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/graphemer": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/has": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.1" }, @@ -5075,16 +4757,14 @@ }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/has-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5094,8 +4774,7 @@ }, "node_modules/has-symbols": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5105,23 +4784,20 @@ }, "node_modules/hexoid": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", - "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -5135,17 +4811,15 @@ }, "node_modules/human-signals": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=10.17.0" } }, "node_modules/iconv-lite": { "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3" }, @@ -5155,9 +4829,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -5171,22 +4842,21 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/ignore": { "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/import-fresh": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -5200,9 +4870,8 @@ }, "node_modules/import-local": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", "dev": true, + "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -5219,18 +4888,16 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5238,14 +4905,12 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "license": "ISC" }, "node_modules/inquirer": { "version": "8.2.5", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.5.tgz", - "integrity": "sha512-QAgPDQMEgrDssk1XiwwHoOGYF9BAbUcc1+j+FhEvaOt8/cKRqyLn0U5qA6F74fGhTMGxf92pOvPBeh29jQJDTQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.1", @@ -5269,17 +4934,15 @@ }, "node_modules/interpret": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/ioredis": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz", - "integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==", + "license": "MIT", "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -5301,23 +4964,20 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, + "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" }, @@ -5327,9 +4987,8 @@ }, "node_modules/is-core-module": { "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", "dev": true, + "license": "MIT", "dependencies": { "has": "^1.0.3" }, @@ -5339,35 +4998,31 @@ }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-generator-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -5377,36 +5032,32 @@ }, "node_modules/is-interactive": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/is-path-inside": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -5416,9 +5067,8 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -5428,29 +5078,25 @@ }, "node_modules/isarray": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", - "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } }, "node_modules/istanbul-lib-instrument": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", @@ -5464,18 +5110,16 @@ }, "node_modules/istanbul-lib-instrument/node_modules/semver": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/istanbul-lib-report": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^3.0.0", @@ -5487,9 +5131,8 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -5501,18 +5144,16 @@ }, "node_modules/istanbul-lib-source-maps/node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/istanbul-reports": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", - "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -5523,17 +5164,15 @@ }, "node_modules/iterare": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", - "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==", + "license": "ISC", "engines": { "node": ">=6" } }, "node_modules/jest": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", - "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^29.5.0", "@jest/types": "^29.5.0", @@ -5557,9 +5196,8 @@ }, "node_modules/jest-changed-files": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", - "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^5.0.0", "p-limit": "^3.1.0" @@ -5570,9 +5208,8 @@ }, "node_modules/jest-circus": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", - "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.5.0", "@jest/expect": "^29.5.0", @@ -5601,9 +5238,8 @@ }, "node_modules/jest-cli": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", - "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/core": "^29.5.0", "@jest/test-result": "^29.5.0", @@ -5635,9 +5271,8 @@ }, "node_modules/jest-config": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", - "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.5.0", @@ -5680,9 +5315,8 @@ }, "node_modules/jest-diff": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", - "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.4.3", @@ -5695,9 +5329,8 @@ }, "node_modules/jest-docblock": { "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", - "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", "dev": true, + "license": "MIT", "dependencies": { "detect-newline": "^3.0.0" }, @@ -5707,9 +5340,8 @@ }, "node_modules/jest-each": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", - "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", "chalk": "^4.0.0", @@ -5723,9 +5355,8 @@ }, "node_modules/jest-environment-node": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", - "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.5.0", "@jest/fake-timers": "^29.5.0", @@ -5740,18 +5371,16 @@ }, "node_modules/jest-get-type": { "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", - "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-haste-map": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", - "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", "@types/graceful-fs": "^4.1.3", @@ -5774,9 +5403,8 @@ }, "node_modules/jest-leak-detector": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", - "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", "dev": true, + "license": "MIT", "dependencies": { "jest-get-type": "^29.4.3", "pretty-format": "^29.5.0" @@ -5787,9 +5415,8 @@ }, "node_modules/jest-matcher-utils": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", - "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.5.0", @@ -5802,9 +5429,8 @@ }, "node_modules/jest-message-util": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", - "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.5.0", @@ -5822,9 +5448,8 @@ }, "node_modules/jest-mock": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", - "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", "@types/node": "*", @@ -5836,9 +5461,8 @@ }, "node_modules/jest-pnp-resolver": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -5853,18 +5477,16 @@ }, "node_modules/jest-regex-util": { "version": "29.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", - "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", "dev": true, + "license": "MIT", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-resolve": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", - "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -5882,9 +5504,8 @@ }, "node_modules/jest-resolve-dependencies": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", - "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", "dev": true, + "license": "MIT", "dependencies": { "jest-regex-util": "^29.4.3", "jest-snapshot": "^29.5.0" @@ -5895,9 +5516,8 @@ }, "node_modules/jest-runner": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", - "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/console": "^29.5.0", "@jest/environment": "^29.5.0", @@ -5927,18 +5547,16 @@ }, "node_modules/jest-runner/node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/jest-runner/node_modules/source-map-support": { "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -5946,9 +5564,8 @@ }, "node_modules/jest-runtime": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", - "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/environment": "^29.5.0", "@jest/fake-timers": "^29.5.0", @@ -5979,9 +5596,8 @@ }, "node_modules/jest-snapshot": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", - "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", "dev": true, + "license": "MIT", "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", @@ -6013,9 +5629,8 @@ }, "node_modules/jest-util": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", - "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", "@types/node": "*", @@ -6030,9 +5645,8 @@ }, "node_modules/jest-validate": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", - "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^29.5.0", "camelcase": "^6.2.0", @@ -6047,9 +5661,8 @@ }, "node_modules/jest-watcher": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", - "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/test-result": "^29.5.0", "@jest/types": "^29.5.0", @@ -6066,9 +5679,8 @@ }, "node_modules/jest-worker": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", - "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "jest-util": "^29.5.0", @@ -6081,9 +5693,8 @@ }, "node_modules/jest-worker/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -6096,15 +5707,13 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -6114,9 +5723,8 @@ }, "node_modules/jsesc": { "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", "dev": true, + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -6126,27 +5734,23 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, + "license": "MIT", "bin": { "json5": "lib/cli.js" }, @@ -6156,15 +5760,13 @@ }, "node_modules/jsonc-parser": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", - "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsonfile": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -6174,27 +5776,24 @@ }, "node_modules/kleur": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/leven": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -6205,29 +5804,25 @@ }, "node_modules/libphonenumber-js": { "version": "1.10.34", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.34.tgz", - "integrity": "sha512-p6g4NaQH4gK1gre32+kV14Mk6GPo2EDcPDvjbi+D2ycsPFsN4gVWNbs0itdHLZqByg6YEK8mE7OeP200I/ScTQ==" + "license": "MIT" }, "node_modules/lines-and-columns": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/loader-runner": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.11.5" } }, "node_modules/locate-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^5.0.0" }, @@ -6240,41 +5835,34 @@ }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "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" }, "node_modules/lodash.defaults": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + "license": "MIT" }, "node_modules/lodash.isarguments": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -6288,23 +5876,20 @@ }, "node_modules/long": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + "license": "Apache-2.0" }, "node_modules/lru-cache": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^3.0.2" } }, "node_modules/macos-release": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz", - "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -6314,9 +5899,8 @@ }, "node_modules/magic-string": { "version": "0.30.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz", - "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.13" }, @@ -6326,9 +5910,8 @@ }, "node_modules/make-dir": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^6.0.0" }, @@ -6341,41 +5924,36 @@ }, "node_modules/make-dir/node_modules/semver": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } }, "node_modules/make-error": { "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/makeerror": { "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "tmpl": "1.0.5" } }, "node_modules/media-typer": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/memfs": { "version": "3.5.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", - "integrity": "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==", "dev": true, + "license": "Unlicense", "dependencies": { "fs-monkey": "^1.0.4" }, @@ -6385,37 +5963,32 @@ }, "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==" + "license": "MIT" }, "node_modules/merge-stream": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/micromatch": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.2", "picomatch": "^2.3.1" @@ -6426,8 +5999,7 @@ }, "node_modules/mime": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -6437,16 +6009,14 @@ }, "node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -6456,18 +6026,16 @@ }, "node_modules/mimic-fn": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/minimatch": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6477,25 +6045,22 @@ }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { "version": "4.2.8", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.2.8.tgz", - "integrity": "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=8" } }, "node_modules/mkdirp": { "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", "dependencies": { "minimist": "^1.2.6" }, @@ -6505,13 +6070,11 @@ }, "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==" + "license": "MIT" }, "node_modules/multer": { "version": "1.4.4-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.4-lts.1.tgz", - "integrity": "sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==", + "license": "MIT", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.0.0", @@ -6527,20 +6090,18 @@ }, "node_modules/mute-stream": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/nanoid": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -6550,49 +6111,49 @@ }, "node_modules/natural-compare": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/natural-compare-lite": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", - "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/nestjs-request-context": { + "version": "2.1.0", + "license": "ISC", + "peerDependencies": { + "@nestjs/common": "^9.0.5" + } }, "node_modules/node-abort-controller": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", - "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-emoji": { "version": "1.11.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", - "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", "dev": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.21" } }, "node_modules/node-fetch": { "version": "2.6.11", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.11.tgz", - "integrity": "sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -6610,30 +6171,26 @@ }, "node_modules/node-int64": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-releases": { "version": "2.0.12", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.12.tgz", - "integrity": "sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/npm-run-path": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.0.0" }, @@ -6643,24 +6200,21 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/object-inspect": { "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -6670,18 +6224,16 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/onetime": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, + "license": "MIT", "dependencies": { "mimic-fn": "^2.1.0" }, @@ -6693,17 +6245,16 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", "dev": true, + "license": "MIT", "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -6711,9 +6262,8 @@ }, "node_modules/ora": { "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", "dev": true, + "license": "MIT", "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -6734,9 +6284,8 @@ }, "node_modules/os-name": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-4.0.1.tgz", - "integrity": "sha512-xl9MAoU97MH1Xt5K9ERft2YfCAoaO6msy1OBA0ozxEC0x0TmIoE6K3QvgJMMZA9yKGLmHXNY/YZoDbiGDj4zYw==", "dev": true, + "license": "MIT", "dependencies": { "macos-release": "^2.5.0", "windows-release": "^4.0.0" @@ -6750,18 +6299,16 @@ }, "node_modules/os-tmpdir": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/p-limit": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -6774,9 +6321,8 @@ }, "node_modules/p-locate": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^3.0.2" }, @@ -6789,18 +6335,16 @@ }, "node_modules/p-try": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -6810,9 +6354,8 @@ }, "node_modules/parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -6828,50 +6371,44 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.9.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.9.2.tgz", - "integrity": "sha512-qSDLy2aGFPm8i4rsbHd4MNyTcrzHFsLQykrtbuGRknZZCBBVXSv2tSCDN2Cg6Rt/GFRw8GoW9y9Ecw5rIPG1sg==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^9.1.1", "minipass": "^5.0.0 || ^6.0.2" @@ -6885,47 +6422,60 @@ }, "node_modules/path-scurry/node_modules/lru-cache": { "version": "9.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-9.1.2.tgz", - "integrity": "sha512-ERJq3FOzJTxBbFjZ7iDs+NiK4VI9Wz+RdrrAB8dio1oV+YvdPzUEE4QNiT2VD51DkIbCYRUUzCRkssXCHqSnKQ==", "dev": true, + "license": "ISC", "engines": { "node": "14 || >=16.14" } }, "node_modules/path-scurry/node_modules/minipass": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-6.0.2.tgz", - "integrity": "sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==", "dev": true, + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/path-source": { + "version": "0.1.3", + "license": "BSD-3-Clause", + "dependencies": { + "array-source": "0.0", + "file-source": "0.6" + } + }, "node_modules/path-to-regexp": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", - "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" + "license": "MIT" }, "node_modules/path-type": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/pbf": { + "version": "3.2.1", + "license": "BSD-3-Clause", + "dependencies": { + "ieee754": "^1.1.12", + "resolve-protobuf-schema": "^2.1.0" + }, + "bin": { + "pbf": "bin/pbf" + } + }, "node_modules/picocolors": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -6935,18 +6485,16 @@ }, "node_modules/pirates": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/pkg-dir": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "license": "MIT", "dependencies": { "find-up": "^4.0.0" }, @@ -6956,9 +6504,8 @@ }, "node_modules/pkg-dir/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -6969,9 +6516,8 @@ }, "node_modules/pkg-dir/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "dependencies": { "p-locate": "^4.1.0" }, @@ -6981,9 +6527,8 @@ }, "node_modules/pkg-dir/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "dependencies": { "p-try": "^2.0.0" }, @@ -6996,9 +6541,8 @@ }, "node_modules/pkg-dir/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "dependencies": { "p-limit": "^2.2.0" }, @@ -7008,27 +6552,24 @@ }, "node_modules/pluralize": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", - "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { "version": "2.8.8", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", - "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin-prettier.js" }, @@ -7041,9 +6582,8 @@ }, "node_modules/prettier-linter-helpers": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -7053,9 +6593,8 @@ }, "node_modules/pretty-format": { "version": "29.5.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", - "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/schemas": "^29.4.3", "ansi-styles": "^5.0.0", @@ -7067,9 +6606,8 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -7079,10 +6617,9 @@ }, "node_modules/prisma": { "version": "4.15.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-4.15.0.tgz", - "integrity": "sha512-iKZZpobPl48gTcSZVawLMQ3lEy6BnXwtoMj7hluoGFYu2kQ6F9LBuBrUyF95zRVnNo8/3KzLXJXJ5TEnLSJFiA==", "devOptional": true, "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@prisma/engines": "4.15.0" }, @@ -7096,19 +6633,16 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + "license": "MIT" }, "node_modules/promise-breaker": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/promise-breaker/-/promise-breaker-5.0.0.tgz", - "integrity": "sha512-mgsWQuG4kJ1dtO6e/QlNDLFtMkMzzecsC69aI5hlLEjGHFNpHrvGhFi4LiK5jg2SMQj74/diH+wZliL9LpGsyA==" + "license": "MIT" }, "node_modules/prompts": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", "dev": true, + "license": "MIT", "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -7119,9 +6653,8 @@ }, "node_modules/protobufjs": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.3.tgz", - "integrity": "sha512-TtpvOqwB5Gdz/PQmOjgsrGH1nHjAQVCN7JG4A6r1sXRWESL5rNMAiRcBQlCAdKxZcAbstExQePYG8xof/JVRgg==", "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -7142,13 +6675,15 @@ }, "node_modules/protobufjs/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" + }, + "node_modules/protocol-buffers-schema": { + "version": "3.6.0", + "license": "MIT" }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -7159,9 +6694,8 @@ }, "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==", "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -7169,17 +6703,14 @@ }, "node_modules/punycode": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/pure-rand": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", "dev": true, "funding": [ { @@ -7190,12 +6721,12 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ] + ], + "license": "MIT" }, "node_modules/qs": { "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.4" }, @@ -7208,299 +6739,10 @@ }, "node_modules/querystringify": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "license": "MIT" }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "dev": true - }, - "node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", - "dev": true, - "dependencies": { - "resolve": "^1.1.6" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/redis-errors": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", - "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", - "engines": { - "node": ">=4" - } - }, - "node_modules/redis-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", - "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", - "dependencies": { - "redis-errors": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/reflect-metadata": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" - }, - "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", - "dev": true, - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-4.4.1.tgz", - "integrity": "sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og==", - "dev": true, - "dependencies": { - "glob": "^9.2.0" - }, - "bin": { - "rimraf": "dist/cjs/src/bin.js" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "9.3.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-9.3.5.tgz", - "integrity": "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "minimatch": "^8.0.2", - "minipass": "^4.2.4", - "path-scurry": "^1.6.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-8.0.4.tgz", - "integrity": "sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { @@ -7516,33 +6758,296 @@ "url": "https://feross.org/support" } ], + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "1.1.14", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/rechoir": { + "version": "0.6.2", + "dev": true, + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "license": "Apache-2.0" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.2", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-protobuf-schema": { + "version": "2.1.0", + "license": "MIT", + "dependencies": { + "protocol-buffers-schema": "^3.3.1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "4.4.1", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^9.2.0" + }, + "bin": { + "rimraf": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "9.3.5", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "minimatch": "^8.0.2", + "minipass": "^4.2.4", + "path-scurry": "^1.6.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "8.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-async": { + "version": "2.4.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } }, "node_modules/rxjs": { "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" } }, "node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "license": "MIT" }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "license": "MIT" }, "node_modules/schema-utils": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.2.0.tgz", - "integrity": "sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==", "dev": true, + "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.8", "ajv": "^6.12.5", @@ -7558,9 +7063,8 @@ }, "node_modules/schema-utils/node_modules/ajv": { "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -7574,24 +7078,21 @@ }, "node_modules/schema-utils/node_modules/ajv-keywords": { "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } }, "node_modules/schema-utils/node_modules/json-schema-traverse": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/semver": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.1.tgz", - "integrity": "sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==", + "version": "7.5.3", "dev": true, + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -7604,9 +7105,8 @@ }, "node_modules/semver/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==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -7616,14 +7116,12 @@ }, "node_modules/semver/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/send": { "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -7645,35 +7143,30 @@ }, "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==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, "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==" + "license": "MIT" }, "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==" + "license": "MIT" }, "node_modules/serialize-javascript": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } }, "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==", + "license": "MIT", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -7686,14 +7179,32 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "license": "ISC" + }, + "node_modules/shapefile": { + "version": "0.6.6", + "license": "BSD-3-Clause", + "dependencies": { + "array-source": "0.0", + "commander": "2", + "path-source": "0.1", + "slice-source": "0.4", + "stream-source": "0.3", + "text-encoding": "^0.6.4" + }, + "bin": { + "dbf2json": "bin/dbf2json", + "shp2json": "bin/shp2json" + } + }, + "node_modules/shapefile/node_modules/commander": { + "version": "2.20.3", + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -7703,18 +7214,16 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/shelljs": { "version": "0.8.5", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", - "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -7729,8 +7238,7 @@ }, "node_modules/side-channel": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.0", "get-intrinsic": "^1.0.2", @@ -7742,39 +7250,38 @@ }, "node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/sisteransi": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/slice-source": { + "version": "0.4.1", + "license": "BSD-3-Clause" + }, "node_modules/source-map": { "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">= 8" } }, "node_modules/source-map-support": { "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -7782,24 +7289,21 @@ }, "node_modules/source-map-support/node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/sprintf-js": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/stack-utils": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -7809,44 +7313,41 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/standard-as-callback": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", - "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + "license": "MIT" }, "node_modules/statuses": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/stream-source": { + "version": "0.3.5", + "license": "BSD-3-Clause" + }, "node_modules/streamsearch": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", "engines": { "node": ">=10.0.0" } }, "node_modules/string_decoder": { "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + "license": "MIT" }, "node_modules/string-length": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, + "license": "MIT", "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -7857,8 +7358,7 @@ }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7870,8 +7370,7 @@ }, "node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7881,27 +7380,24 @@ }, "node_modules/strip-bom": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -7911,9 +7407,8 @@ }, "node_modules/superagent": { "version": "8.0.9", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.9.tgz", - "integrity": "sha512-4C7Bh5pyHTvU33KpZgwrNKh/VQnvgtCSqPRfJAUdmrtSYePVzVg4E4OzsrbkhJj9O7SO6Bnv75K/F8XVZT8YHA==", "dev": true, + "license": "MIT", "dependencies": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.4", @@ -7932,9 +7427,8 @@ }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -7944,9 +7438,8 @@ }, "node_modules/supertest": { "version": "6.3.3", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz", - "integrity": "sha512-EMCG6G8gDu5qEqRQ3JjjPs6+FYT1a7Hv5ApHvtSghmOFJYtsU5S+pSb6Y2EUeCEY3CmEL3mmQ8YWlPOzQomabA==", "dev": true, + "license": "MIT", "dependencies": { "methods": "^1.1.2", "superagent": "^8.0.5" @@ -7957,8 +7450,7 @@ }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -7968,9 +7460,8 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -7980,27 +7471,24 @@ }, "node_modules/symbol-observable": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", - "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10" } }, "node_modules/tapable": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/terser": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.18.0.tgz", - "integrity": "sha512-pdL757Ig5a0I+owA42l6tIuEycRuM7FPY4n62h44mRLRfnOxJkkOHd6i89dOpwZlpF6JXBwaAHF6yWzFrt+QyA==", + "version": "5.17.7", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -8016,9 +7504,8 @@ }, "node_modules/terser-webpack-plugin": { "version": "5.3.9", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", - "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", "dev": true, + "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "jest-worker": "^27.4.5", @@ -8050,9 +7537,8 @@ }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", @@ -8064,9 +7550,8 @@ }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -8079,15 +7564,13 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/test-exclude": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -8097,23 +7580,31 @@ "node": ">=8" } }, + "node_modules/text-encoding": { + "version": "0.6.4", + "license": "Unlicense" + }, "node_modules/text-table": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true + "dev": true, + "license": "MIT" + }, + "node_modules/timezonecomplete": { + "version": "5.12.4", + "license": "MIT", + "dependencies": { + "tzdata": "^1.0.25" + } }, "node_modules/tmp": { "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, + "license": "MIT", "dependencies": { "os-tmpdir": "~1.0.2" }, @@ -8123,24 +7614,21 @@ }, "node_modules/tmpl": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/to-fast-properties": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -8150,31 +7638,27 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", "engines": { "node": ">=0.6" } }, "node_modules/tr46": { "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "license": "MIT" }, "node_modules/tree-kill": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", "dev": true, + "license": "MIT", "bin": { "tree-kill": "cli.js" } }, "node_modules/ts-jest": { "version": "29.0.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.0.5.tgz", - "integrity": "sha512-PL3UciSgIpQ7f6XjVOmbi96vmDHUqAyqDr8YxzopDqX3kfgYtX1cuNeBjP+L9sFXi6nzsGGA6R3fP3DDDJyrxA==", "dev": true, + "license": "MIT", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -8215,9 +7699,8 @@ }, "node_modules/ts-loader": { "version": "9.4.3", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.3.tgz", - "integrity": "sha512-n3hBnm6ozJYzwiwt5YRiJZkzktftRpMiBApHaJPoWLA+qetQBAXkHqCLM6nwSdRDimqVtA5ocIkcTRLMTt7yzA==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", @@ -8234,9 +7717,8 @@ }, "node_modules/ts-node": { "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, + "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -8277,9 +7759,8 @@ }, "node_modules/tsconfig-paths": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", - "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", "dev": true, + "license": "MIT", "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", @@ -8291,9 +7772,8 @@ }, "node_modules/tsconfig-paths-webpack-plugin": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/tsconfig-paths-webpack-plugin/-/tsconfig-paths-webpack-plugin-4.0.1.tgz", - "integrity": "sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.7.0", @@ -8305,23 +7785,20 @@ }, "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/tslib": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz", - "integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==" + "license": "0BSD" }, "node_modules/tsutils": { "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, + "license": "MIT", "dependencies": { "tslib": "^1.8.1" }, @@ -8334,15 +7811,13 @@ }, "node_modules/tsutils/node_modules/tslib": { "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -8352,17 +7827,15 @@ }, "node_modules/type-detect": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, "node_modules/type-fest": { "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -8372,8 +7845,7 @@ }, "node_modules/type-is": { "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -8384,14 +7856,12 @@ }, "node_modules/typedarray": { "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + "license": "MIT" }, "node_modules/typescript": { "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8400,10 +7870,13 @@ "node": ">=4.2.0" } }, + "node_modules/tzdata": { + "version": "1.0.38", + "license": "MIT" + }, "node_modules/uid": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/uid/-/uid-2.0.2.tgz", - "integrity": "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==", + "license": "MIT", "dependencies": { "@lukeed/csprng": "^1.0.0" }, @@ -8413,25 +7886,21 @@ }, "node_modules/universalify": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", "dev": true, "funding": [ { @@ -8447,6 +7916,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "escalade": "^3.1.1", "picocolors": "^1.0.0" @@ -8460,17 +7930,15 @@ }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, "node_modules/url-parse": { "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "license": "MIT", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -8478,36 +7946,31 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } }, "node_modules/uuid": { "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/v8-to-istanbul": { "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -8519,40 +7982,35 @@ }, "node_modules/v8-to-istanbul/node_modules/convert-source-map": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/validator": { "version": "13.9.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-13.9.0.tgz", - "integrity": "sha512-B+dGG8U3fdtM0/aNK4/X8CXq/EcxU2WPrPEkJGslb47qyHsxmbggTWK0yEA4qnYVNF+nxNlN88o14hIcPmSIEA==", + "license": "MIT", "engines": { "node": ">= 0.10" } }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/walker": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "makeerror": "1.0.12" } }, "node_modules/watchpack": { "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", "dev": true, + "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" @@ -8563,23 +8021,20 @@ }, "node_modules/wcwidth": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, + "license": "MIT", "dependencies": { "defaults": "^1.0.3" } }, "node_modules/webidl-conversions": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "license": "BSD-2-Clause" }, "node_modules/webpack": { "version": "5.86.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.86.0.tgz", - "integrity": "sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -8625,26 +8080,23 @@ }, "node_modules/webpack-node-externals": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-3.0.0.tgz", - "integrity": "sha512-LnL6Z3GGDPht/AigwRh2dvL9PQPFQ8skEpVrWZXLWBYmqcaojHNN0onvHzie6rq7EWKrrBfPYqNEzTJgiwEQDQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/webpack-sources": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.13.0" } }, "node_modules/whatwg-url": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -8652,9 +8104,8 @@ }, "node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -8667,8 +8118,7 @@ }, "node_modules/widest-line": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", - "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "license": "MIT", "dependencies": { "string-width": "^4.0.0" }, @@ -8678,9 +8128,8 @@ }, "node_modules/windows-release": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-4.0.0.tgz", - "integrity": "sha512-OxmV4wzDKB1x7AZaZgXMVsdJ1qER1ed83ZrTYd5Bwq2HfJVg3DJS8nqlAG4sMoJ7mu8cuRmLEYyU13BKwctRAg==", "dev": true, + "license": "MIT", "dependencies": { "execa": "^4.0.2" }, @@ -8693,9 +8142,8 @@ }, "node_modules/windows-release/node_modules/execa": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, + "license": "MIT", "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", @@ -8716,9 +8164,8 @@ }, "node_modules/windows-release/node_modules/get-stream": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -8731,26 +8178,15 @@ }, "node_modules/windows-release/node_modules/human-signals": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=8.12.0" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8765,15 +8201,13 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/write-file-atomic": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dev": true, + "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" @@ -8784,39 +8218,34 @@ }, "node_modules/xtend": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", "engines": { "node": ">=0.4" } }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/yaml": { "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", "dev": true, + "license": "ISC", "engines": { "node": ">= 6" } }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -8832,26 +8261,23 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", "engines": { "node": ">=12" } }, "node_modules/yn": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/yocto-queue": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, diff --git a/package.json b/package.json index 0caf024..c8bc9a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mobicoop/ad", - "version": "0.0.1", + "version": "1.0.0", "description": "Mobicoop V3 Ad", "author": "sbriat", "private": true, @@ -34,9 +34,6 @@ "migrate:deploy": "npx prisma migrate deploy" }, "dependencies": { - "@automapper/classes": "^8.7.7", - "@automapper/core": "^8.7.7", - "@automapper/nestjs": "^8.7.7", "@grpc/grpc-js": "^1.8.14", "@grpc/proto-loader": "^0.7.6", "@liaoliaots/nestjs-redis": "^9.0.5", @@ -46,15 +43,19 @@ "@nestjs/config": "^2.3.1", "@nestjs/core": "^9.0.0", "@nestjs/cqrs": "^9.0.3", + "@nestjs/event-emitter": "^1.4.2", "@nestjs/microservices": "^9.4.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/terminus": "^9.2.2", "@prisma/client": "^4.13.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "geo-tz": "^7.0.7", "ioredis": "^5.3.2", + "nestjs-request-context": "^2.1.0", "reflect-metadata": "^0.1.13", - "rxjs": "^7.2.0" + "rxjs": "^7.2.0", + "timezonecomplete": "^5.12.4" }, "devDependencies": { "@nestjs/cli": "^9.0.0", @@ -64,6 +65,7 @@ "@types/jest": "29.5.0", "@types/node": "18.15.11", "@types/supertest": "^2.0.11", + "@types/uuid": "^9.0.2", "@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/parser": "^5.0.0", "dotenv-cli": "^7.2.1", @@ -88,13 +90,16 @@ "ts" ], "modulePathIgnorePatterns": [ - ".controller.ts", ".module.ts", - ".request.ts", - ".presenter.ts", - ".profile.ts", - ".exception.ts", + ".dto.ts", ".constants.ts", + ".response.ts", + ".response.base.ts", + ".port.ts", + "libs/exceptions", + "libs/types", + "prisma.service.ts", + "convert-props-to-object.util.ts", "main.ts" ], "rootDir": "src", @@ -106,18 +111,24 @@ "**/*.(t|j)s" ], "coveragePathIgnorePatterns": [ - ".validator.ts", - ".controller.ts", ".module.ts", - ".request.ts", - ".presenter.ts", - ".profile.ts", - ".exception.ts", + ".dto.ts", ".constants.ts", - ".interfaces.ts", + ".response.ts", + ".response.base.ts", + ".port.ts", + "libs/exceptions", + "libs/types", + "prisma.service.ts", + "convert-props-to-object.util.ts", "main.ts" ], "coverageDirectory": "../coverage", + "moduleNameMapper": { + "^@libs(.*)": "/libs/$1", + "^@modules(.*)": "/modules/$1", + "^@src(.*)": "$1" + }, "testEnvironment": "node" } } diff --git a/prisma/migrations/20230515131219_init/migration.sql b/prisma/migrations/20230623091500_init/migration.sql similarity index 67% rename from prisma/migrations/20230515131219_init/migration.sql rename to prisma/migrations/20230623091500_init/migration.sql index daef9bb..f354d9a 100644 --- a/prisma/migrations/20230515131219_init/migration.sql +++ b/prisma/migrations/20230623091500_init/migration.sql @@ -10,13 +10,13 @@ CREATE TABLE "ad" ( "frequency" "Frequency" NOT NULL, "fromDate" DATE NOT NULL, "toDate" DATE NOT NULL, - "monTime" TEXT, - "tueTime" TEXT, - "wedTime" TEXT, - "thuTime" TEXT, - "friTime" TEXT, - "satTime" TEXT, - "sunTime" TEXT, + "monTime" TIMESTAMPTZ, + "tueTime" TIMESTAMPTZ, + "wedTime" TIMESTAMPTZ, + "thuTime" TIMESTAMPTZ, + "friTime" TIMESTAMPTZ, + "satTime" TIMESTAMPTZ, + "sunTime" TIMESTAMPTZ, "monMargin" INTEGER NOT NULL, "tueMargin" INTEGER NOT NULL, "wedMargin" INTEGER NOT NULL, @@ -24,8 +24,8 @@ CREATE TABLE "ad" ( "friMargin" INTEGER NOT NULL, "satMargin" INTEGER NOT NULL, "sunMargin" INTEGER NOT NULL, - "seatsDriver" SMALLINT NOT NULL, - "seatsPassenger" SMALLINT NOT NULL, + "seatsProposed" SMALLINT NOT NULL, + "seatsRequested" SMALLINT NOT NULL, "strict" BOOLEAN NOT NULL, "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -34,12 +34,12 @@ CREATE TABLE "ad" ( ); -- CreateTable -CREATE TABLE "address" ( +CREATE TABLE "waypoint" ( "uuid" UUID NOT NULL, "adUuid" UUID NOT NULL, "position" SMALLINT NOT NULL, - "lon" DOUBLE PRECISION NOT NULL, - "lat" DOUBLE PRECISION NOT NULL, + "lon" DECIMAL(9,6) NOT NULL, + "lat" DECIMAL(8,6) NOT NULL, "name" TEXT, "houseNumber" TEXT, "street" TEXT, @@ -49,8 +49,8 @@ CREATE TABLE "address" ( "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "address_pkey" PRIMARY KEY ("uuid") + CONSTRAINT "waypoint_pkey" PRIMARY KEY ("uuid") ); -- AddForeignKey -ALTER TABLE "address" ADD CONSTRAINT "address_adUuid_fkey" FOREIGN KEY ("adUuid") REFERENCES "ad"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE "waypoint" ADD CONSTRAINT "waypoint_adUuid_fkey" FOREIGN KEY ("adUuid") REFERENCES "ad"("uuid") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index c6dbca0..91e2e9a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,20 +12,20 @@ datasource db { } model Ad { - uuid String @id @default(uuid()) @db.Uuid - userUuid String @db.Uuid + uuid String @id @default(uuid()) @db.Uuid + userUuid String @db.Uuid driver Boolean passenger Boolean frequency Frequency - fromDate DateTime @db.Date - toDate DateTime @db.Date - monTime String? - tueTime String? - wedTime String? - thuTime String? - friTime String? - satTime String? - sunTime String? + fromDate DateTime @db.Date + toDate DateTime @db.Date + monTime DateTime? @db.Timestamptz() + tueTime DateTime? @db.Timestamptz() + wedTime DateTime? @db.Timestamptz() + thuTime DateTime? @db.Timestamptz() + friTime DateTime? @db.Timestamptz() + satTime DateTime? @db.Timestamptz() + sunTime DateTime? @db.Timestamptz() monMargin Int tueMargin Int wedMargin Int @@ -33,22 +33,22 @@ model Ad { friMargin Int satMargin Int sunMargin Int - seatsDriver Int @db.SmallInt - seatsPassenger Int @db.SmallInt + seatsProposed Int @db.SmallInt + seatsRequested Int @db.SmallInt strict Boolean - createdAt DateTime @default(now()) - updatedAt DateTime @default(now()) @updatedAt - addresses Address[] + createdAt DateTime @default(now()) + updatedAt DateTime @default(now()) @updatedAt + waypoints Waypoint[] @@map("ad") } -model Address { +model Waypoint { uuid String @id @default(uuid()) @db.Uuid adUuid String @db.Uuid position Int @db.SmallInt - lon Float - lat Float + lon Decimal @db.Decimal(9, 6) + lat Decimal @db.Decimal(8, 6) name String? houseNumber String? street String? @@ -59,7 +59,7 @@ model Address { updatedAt DateTime @default(now()) @updatedAt Ad Ad @relation(fields: [adUuid], references: [uuid], onDelete: Cascade) - @@map("address") + @@map("waypoint") } enum Frequency { diff --git a/src/app.module.ts b/src/app.module.ts index d7b8f1d..a2b6bc2 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,9 +1,6 @@ import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; -import { HealthModule } from './modules/health/health.module'; import { AdModule } from './modules/ad/ad.module'; -import { AutomapperModule } from '@automapper/nestjs'; -import { classes } from '@automapper/classes'; import { MessageBrokerModule, MessageBrokerModuleOptions, @@ -12,11 +9,15 @@ import { ConfigurationModule, ConfigurationModuleOptions, } from '@mobicoop/configuration-module'; +import { EventEmitterModule } from '@nestjs/event-emitter'; +import { RequestContextModule } from 'nestjs-request-context'; +import { HealthModule } from '@modules/health/health.module'; @Module({ imports: [ ConfigModule.forRoot({ isGlobal: true }), - AutomapperModule.forRoot({ strategyInitializer: classes() }), + EventEmitterModule.forRoot(), + RequestContextModule, MessageBrokerModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], @@ -44,8 +45,6 @@ import { password: configService.get('REDIS_PASSWORD'), port: configService.get('REDIS_PORT'), }, - - propagateConfigurationRoutingKey: 'configuration.propagate', setConfigurationBrokerQueue: 'ad-configuration-create-update', deleteConfigurationQueue: 'ad-configuration-delete', propagateConfigurationQueue: 'ad-configuration-propagate', @@ -54,7 +53,5 @@ import { HealthModule, AdModule, ], - controllers: [], - providers: [], }) export class AppModule {} diff --git a/src/libs/api/api-error.response.ts b/src/libs/api/api-error.response.ts new file mode 100644 index 0000000..db47f4c --- /dev/null +++ b/src/libs/api/api-error.response.ts @@ -0,0 +1,19 @@ +export class ApiErrorResponse { + readonly statusCode: number; + + readonly message: string; + + readonly error: string; + + readonly correlationId: string; + + readonly subErrors?: string[]; + + constructor(body: ApiErrorResponse) { + this.statusCode = body.statusCode; + this.message = body.message; + this.error = body.error; + this.correlationId = body.correlationId; + this.subErrors = body.subErrors; + } +} diff --git a/src/libs/api/id.response.dto.ts b/src/libs/api/id.response.dto.ts new file mode 100644 index 0000000..aa995e7 --- /dev/null +++ b/src/libs/api/id.response.dto.ts @@ -0,0 +1,7 @@ +export class IdResponse { + constructor(id: string) { + this.id = id; + } + + readonly id: string; +} diff --git a/src/libs/api/paginated.response.base.ts b/src/libs/api/paginated.response.base.ts new file mode 100644 index 0000000..f11d849 --- /dev/null +++ b/src/libs/api/paginated.response.base.ts @@ -0,0 +1,8 @@ +import { Paginated } from '../ddd'; + +export abstract class PaginatedResponseDto extends Paginated { + readonly total: number; + readonly perPage: number; + readonly page: number; + abstract readonly data: readonly T[]; +} diff --git a/src/libs/api/response.base.ts b/src/libs/api/response.base.ts new file mode 100644 index 0000000..58dc0e6 --- /dev/null +++ b/src/libs/api/response.base.ts @@ -0,0 +1,23 @@ +import { IdResponse } from './id.response.dto'; + +export interface BaseResponseProps { + id: string; + createdAt: Date; + updatedAt: Date; +} + +/** + * Most of our response objects will have properties like + * id, createdAt and updatedAt so we can move them to a + * separate class and extend it to avoid duplication. + */ +export class ResponseBase extends IdResponse { + constructor(props: BaseResponseProps) { + super(props.id); + this.createdAt = new Date(props.createdAt).toISOString(); + this.updatedAt = new Date(props.updatedAt).toISOString(); + } + + readonly createdAt: string; + readonly updatedAt: string; +} diff --git a/src/libs/db/prisma-repository.base.ts b/src/libs/db/prisma-repository.base.ts new file mode 100644 index 0000000..cfb1ed7 --- /dev/null +++ b/src/libs/db/prisma-repository.base.ts @@ -0,0 +1,66 @@ +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { AggregateRoot, Mapper, RepositoryPort } from '../ddd'; +import { ObjectLiteral } from '../types'; +import { LoggerPort } from '../ports/logger.port'; +import { + PrismaRawRepositoryPort, + PrismaRepositoryPort, +} from '../ports/prisma-repository.port'; +import { Prisma } from '@prisma/client'; +import { + ConflictException, + DatabaseErrorException, + NotFoundException, +} from '@libs/exceptions'; + +export abstract class PrismaRepositoryBase< + Aggregate extends AggregateRoot, + DbReadModel extends ObjectLiteral, + DbWriteModel extends ObjectLiteral, +> implements RepositoryPort +{ + protected constructor( + protected readonly prisma: PrismaRepositoryPort | any, + protected readonly prismaRaw: PrismaRawRepositoryPort, + protected readonly mapper: Mapper, + protected readonly eventEmitter: EventEmitter2, + protected readonly logger: LoggerPort, + ) {} + + async findOneById(id: string, include?: any): Promise { + const entity = await this.prisma.findUnique({ + where: { uuid: id }, + include, + }); + if (entity) return this.mapper.toDomain(entity); + throw new NotFoundException('Record not found'); + } + + async insert(entity: Aggregate): Promise { + try { + await this.prisma.create({ + data: this.mapper.toPersistence(entity), + }); + entity.publishEvents(this.logger, this.eventEmitter); + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + if (e.message.includes('Already exists')) { + throw new ConflictException('Record already exists', e); + } + } + throw e; + } + } + + async healthCheck(): Promise { + try { + await this.prismaRaw.$queryRaw`SELECT 1`; + return true; + } catch (e) { + if (e instanceof Prisma.PrismaClientKnownRequestError) { + throw new DatabaseErrorException(e.message); + } + throw new DatabaseErrorException(); + } + } +} diff --git a/src/modules/database/adapters/secondaries/prisma-service.ts b/src/libs/db/prisma.service.ts similarity index 100% rename from src/modules/database/adapters/secondaries/prisma-service.ts rename to src/libs/db/prisma.service.ts diff --git a/src/libs/ddd/aggregate-root.base.ts b/src/libs/ddd/aggregate-root.base.ts new file mode 100644 index 0000000..a887396 --- /dev/null +++ b/src/libs/ddd/aggregate-root.base.ts @@ -0,0 +1,35 @@ +import { Entity } from './entity.base'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { LoggerPort } from '@libs/ports/logger.port'; +import { DomainEvent } from './domain-event.base'; + +export abstract class AggregateRoot extends Entity { + private _domainEvents: DomainEvent[] = []; + + get domainEvents(): DomainEvent[] { + return this._domainEvents; + } + + protected addEvent(domainEvent: DomainEvent): void { + this._domainEvents.push(domainEvent); + } + + public clearEvents(): void { + this._domainEvents = []; + } + + public async publishEvents( + logger: LoggerPort, + eventEmitter: EventEmitter2, + ): Promise { + await Promise.all( + this.domainEvents.map(async (event) => { + logger.debug( + `"${event.constructor.name}" event published for aggregate ${this.constructor.name} : ${this.id}`, + ); + return eventEmitter.emitAsync(event.constructor.name, event); + }), + ); + this.clearEvents(); + } +} diff --git a/src/libs/ddd/command.base.ts b/src/libs/ddd/command.base.ts new file mode 100644 index 0000000..046db48 --- /dev/null +++ b/src/libs/ddd/command.base.ts @@ -0,0 +1,52 @@ +import { v4 } from 'uuid'; +import { ArgumentNotProvidedException } from '../exceptions'; +import { Guard } from '../guard'; + +export type CommandProps = Omit & Partial; + +type CommandMetadata = { + /** ID for correlation purposes (for commands that + * arrive from other microservices,logs correlation, etc). */ + readonly correlationId: string; + + /** + * Causation id to reconstruct execution order if needed + */ + readonly causationId?: string; + + /** + * ID of a user who invoker the command. Can be useful for + * logging and tracking execution of commands and events + */ + readonly userId?: string; + + /** + * Time when the command occurred. Mostly for tracing purposes + */ + readonly timestamp: number; +}; + +export class Command { + /** + * Command id, in case if we want to save it + * for auditing purposes and create a correlation/causation chain + */ + readonly id: string; + + readonly metadata: CommandMetadata; + + constructor(props: CommandProps) { + if (Guard.isEmpty(props)) { + throw new ArgumentNotProvidedException( + 'Command props should not be empty', + ); + } + this.id = props.id || v4(); + this.metadata = { + correlationId: props?.metadata?.correlationId, + causationId: props?.metadata?.causationId, + timestamp: props?.metadata?.timestamp || Date.now(), + userId: props?.metadata?.userId, + }; + } +} diff --git a/src/libs/ddd/domain-event.base.ts b/src/libs/ddd/domain-event.base.ts new file mode 100644 index 0000000..c905dca --- /dev/null +++ b/src/libs/ddd/domain-event.base.ts @@ -0,0 +1,52 @@ +import { ArgumentNotProvidedException } from '../exceptions'; +import { Guard } from '../guard'; +import { v4 } from 'uuid'; + +type DomainEventMetadata = { + /** Timestamp when this domain event occurred */ + readonly timestamp: number; + + /** ID for correlation purposes (for Integration Events,logs correlation, etc). + */ + readonly correlationId: string; + + /** + * Causation id used to reconstruct execution order if needed + */ + readonly causationId?: string; + + /** + * User ID for debugging and logging purposes + */ + readonly userId?: string; +}; + +export type DomainEventProps = Omit & { + aggregateId: string; + metadata?: DomainEventMetadata; +}; + +export abstract class DomainEvent { + public readonly id: string; + + /** Aggregate ID where domain event occurred */ + public readonly aggregateId: string; + + public readonly metadata: DomainEventMetadata; + + constructor(props: DomainEventProps) { + if (Guard.isEmpty(props)) { + throw new ArgumentNotProvidedException( + 'DomainEvent props should not be empty', + ); + } + this.id = v4(); + this.aggregateId = props.aggregateId; + this.metadata = { + correlationId: props?.metadata?.correlationId, + causationId: props?.metadata?.causationId, + timestamp: props?.metadata?.timestamp || Date.now(), + userId: props?.metadata?.userId, + }; + } +} diff --git a/src/libs/ddd/entity.base.ts b/src/libs/ddd/entity.base.ts new file mode 100644 index 0000000..b946f43 --- /dev/null +++ b/src/libs/ddd/entity.base.ts @@ -0,0 +1,150 @@ +import { + ArgumentNotProvidedException, + ArgumentInvalidException, + ArgumentOutOfRangeException, +} from '../exceptions'; +import { Guard } from '../guard'; +import { convertPropsToObject } from '../utils'; + +export type AggregateID = string; + +export interface BaseEntityProps { + id: AggregateID; + createdAt: Date; + updatedAt: Date; +} + +export interface CreateEntityProps { + id: AggregateID; + props: T; + createdAt?: Date; + updatedAt?: Date; +} + +export abstract class Entity { + constructor({ + id, + createdAt, + updatedAt, + props, + }: CreateEntityProps) { + this.setId(id); + this.validateProps(props); + const now = new Date(); + this._createdAt = createdAt || now; + this._updatedAt = updatedAt || now; + this.props = props; + this.validate(); + } + + protected readonly props: EntityProps; + + /** + * ID is set in the concrete entity implementation to support + * different ID types depending on your needs. + * For example it could be a UUID for aggregate root, + * and shortid / nanoid for child entities. + */ + protected abstract _id: AggregateID; + + private readonly _createdAt: Date; + + private _updatedAt: Date; + + get id(): AggregateID { + return this._id; + } + + private setId(id: AggregateID): void { + this._id = id; + } + + get createdAt(): Date { + return this._createdAt; + } + + get updatedAt(): Date { + return this._updatedAt; + } + + static isEntity(entity: unknown): entity is Entity { + return entity instanceof Entity; + } + + /** + * Checks if two entities are the same Entity by comparing ID field. + * @param object Entity + */ + public equals(object?: Entity): boolean { + if (object === null || object === undefined) { + return false; + } + + if (this === object) { + return true; + } + + if (!Entity.isEntity(object)) { + return false; + } + + return this.id ? this.id === object.id : false; + } + + /** + * Returns entity properties. + * @return {*} {Props & EntityProps} + * @memberof Entity + */ + public getProps(): EntityProps & BaseEntityProps { + const propsCopy = { + id: this._id, + createdAt: this._createdAt, + updatedAt: this._updatedAt, + ...this.props, + }; + return Object.freeze(propsCopy); + } + + /** + * Convert an Entity and all sub-entities/Value Objects it + * contains to a plain object with primitive types. Can be + * useful when logging an entity during testing/debugging + */ + public toObject(): unknown { + const plainProps = convertPropsToObject(this.props); + + const result = { + id: this._id, + createdAt: this._createdAt, + updatedAt: this._updatedAt, + ...plainProps, + }; + return Object.freeze(result); + } + + /** + * There are certain rules that always have to be true (invariants) + * for each entity. Validate method is called every time before + * saving an entity to the database to make sure those rules are respected. + */ + public abstract validate(): void; + + private validateProps(props: EntityProps): void { + const MAX_PROPS = 50; + + if (Guard.isEmpty(props)) { + throw new ArgumentNotProvidedException( + 'Entity props should not be empty', + ); + } + if (typeof props !== 'object') { + throw new ArgumentInvalidException('Entity props should be an object'); + } + if (Object.keys(props as any).length > MAX_PROPS) { + throw new ArgumentOutOfRangeException( + `Entity props should not have more than ${MAX_PROPS} properties`, + ); + } + } +} diff --git a/src/libs/ddd/index.ts b/src/libs/ddd/index.ts new file mode 100644 index 0000000..e0e4a3a --- /dev/null +++ b/src/libs/ddd/index.ts @@ -0,0 +1,7 @@ +export * from './aggregate-root.base'; +export * from './command.base'; +export * from './domain-event.base'; +export * from './entity.base'; +export * from './mapper.interface'; +export * from './repository.port'; +export * from './value-object.base'; diff --git a/src/libs/ddd/mapper.interface.ts b/src/libs/ddd/mapper.interface.ts new file mode 100644 index 0000000..8e9e3a4 --- /dev/null +++ b/src/libs/ddd/mapper.interface.ts @@ -0,0 +1,12 @@ +import { Entity } from './entity.base'; + +export interface Mapper< + DomainEntity extends Entity, + DbReadRecord, + DbWriteRecord, + Response = any, +> { + toPersistence(entity: DomainEntity): DbWriteRecord; + toDomain(record: DbReadRecord): DomainEntity; + toResponse(entity: DomainEntity): Response; +} diff --git a/src/libs/ddd/query.base.ts b/src/libs/ddd/query.base.ts new file mode 100644 index 0000000..7aba63c --- /dev/null +++ b/src/libs/ddd/query.base.ts @@ -0,0 +1,31 @@ +import { OrderBy, PaginatedQueryParams } from './repository.port'; + +/** + * Base class for regular queries + */ +export abstract class QueryBase {} + +/** + * Base class for paginated queries + */ +export abstract class PaginatedQueryBase extends QueryBase { + perPage: number; + offset: number; + orderBy: OrderBy; + page: number; + + constructor(props: PaginatedParams) { + super(); + this.perPage = props.perPage || 10; + this.offset = props.page ? props.page * this.perPage : 0; + this.page = props.page || 0; + this.orderBy = props.orderBy || { field: true, param: 'desc' }; + } +} + +// Paginated query parameters +export type PaginatedParams = Omit< + T, + 'perPage' | 'offset' | 'orderBy' | 'page' +> & + Partial>; diff --git a/src/libs/ddd/repository.port.ts b/src/libs/ddd/repository.port.ts new file mode 100644 index 0000000..66d8450 --- /dev/null +++ b/src/libs/ddd/repository.port.ts @@ -0,0 +1,40 @@ +/* Most of repositories will probably need generic + save/find/delete operations, so it's easier + to have some shared interfaces. + More specific queries should be defined + in a respective repository. +*/ + +export class Paginated { + readonly total: number; + readonly perPage: number; + readonly page: number; + readonly data: readonly T[]; + + constructor(props: Paginated) { + this.total = props.total; + this.perPage = props.perPage; + this.page = props.page; + this.data = props.data; + } +} + +export type OrderBy = { field: string | true; param: 'asc' | 'desc' }; + +export type PaginatedQueryParams = { + perPage: number; + page: number; + offset: number; + orderBy: OrderBy; +}; + +export interface RepositoryPort { + insert(entity: Entity | Entity[]): Promise; + findOneById(id: string, include?: any): Promise; + healthCheck(): Promise; + // findAll(): Promise; + // findAllPaginated(params: PaginatedQueryParams): Promise>; + // delete(entity: Entity): Promise; + + // transaction(handler: () => Promise): Promise; +} diff --git a/src/libs/ddd/value-object.base.ts b/src/libs/ddd/value-object.base.ts new file mode 100644 index 0000000..348d7a9 --- /dev/null +++ b/src/libs/ddd/value-object.base.ts @@ -0,0 +1,71 @@ +import { ArgumentNotProvidedException } from '../exceptions'; +import { Guard } from '../guard'; +import { convertPropsToObject } from '../utils'; + +/** + * Domain Primitive is an object that contains only a single value + */ +export type Primitives = string | number | boolean; +export interface DomainPrimitive { + value: T; +} + +type ValueObjectProps = T extends Primitives | Date ? DomainPrimitive : T; + +export abstract class ValueObject { + protected readonly props: ValueObjectProps; + + constructor(props: ValueObjectProps) { + this.checkIfEmpty(props); + this.validate(props); + this.props = props; + } + + protected abstract validate(props: ValueObjectProps): void; + + static isValueObject(obj: unknown): obj is ValueObject { + return obj instanceof ValueObject; + } + + /** + * Check if two Value Objects are equal. Checks structural equality. + * @param vo ValueObject + */ + public equals(vo?: ValueObject): boolean { + if (vo === null || vo === undefined) { + return false; + } + return JSON.stringify(this) === JSON.stringify(vo); + } + + /** + * Unpack a value object to get its raw properties + */ + public unpack(): T { + if (this.isDomainPrimitive(this.props)) { + return this.props.value; + } + + const propsCopy = convertPropsToObject(this.props); + + return Object.freeze(propsCopy); + } + + private checkIfEmpty(props: ValueObjectProps): void { + if ( + Guard.isEmpty(props) || + (this.isDomainPrimitive(props) && Guard.isEmpty(props.value)) + ) { + throw new ArgumentNotProvidedException('Property cannot be empty'); + } + } + + private isDomainPrimitive( + obj: unknown, + ): obj is DomainPrimitive { + if (Object.prototype.hasOwnProperty.call(obj, 'value')) { + return true; + } + return false; + } +} diff --git a/src/libs/exceptions/exception.base.ts b/src/libs/exceptions/exception.base.ts new file mode 100644 index 0000000..4c9ebbc --- /dev/null +++ b/src/libs/exceptions/exception.base.ts @@ -0,0 +1,63 @@ +export interface SerializedException { + message: string; + code: string; + correlationId: string; + stack?: string; + cause?: string; + metadata?: unknown; + /** + * ^ Consider adding optional `metadata` object to + * exceptions (if language doesn't support anything + * similar by default) and pass some useful technical + * information about the exception when throwing. + * This will make debugging easier. + */ +} + +/** + * Base class for custom exceptions. + * + * @abstract + * @class ExceptionBase + * @extends {Error} + */ +export abstract class ExceptionBase extends Error { + abstract code: string; + + public readonly correlationId: string; + + /** + * @param {string} message + * @param {ObjectLiteral} [metadata={}] + * **BE CAREFUL** not to include sensitive info in 'metadata' + * to prevent leaks since all exception's data will end up + * in application's log files. Only include non-sensitive + * info that may help with debugging. + */ + constructor( + readonly message: string, + readonly cause?: Error, + readonly metadata?: unknown, + ) { + super(message); + Error.captureStackTrace(this, this.constructor); + } + + /** + * By default in NodeJS Error objects are not + * serialized properly when sending plain objects + * to external processes. This method is a workaround. + * Keep in mind not to return a stack trace to user when in production. + * https://iaincollins.medium.com/error-handling-in-javascript-a6172ccdf9af + */ + toJSON(): SerializedException { + return { + message: this.message, + code: this.code, + stack: this.stack, + correlationId: this.correlationId, + cause: JSON.stringify(this.cause), + metadata: this.metadata, + }; + } +} diff --git a/src/libs/exceptions/exception.codes.ts b/src/libs/exceptions/exception.codes.ts new file mode 100644 index 0000000..5ca541b --- /dev/null +++ b/src/libs/exceptions/exception.codes.ts @@ -0,0 +1,16 @@ +/** + * Adding a `code` string with a custom status code for every + * exception is a good practice, since when that exception + * is transferred to another process `instanceof` check + * cannot be performed anymore so a `code` string is used instead. + * code constants can be stored in a separate file so they + * can be shared and reused on a receiving side (code sharing is + * useful when developing fullstack apps or microservices) + */ +export const ARGUMENT_INVALID = 'GENERIC.ARGUMENT_INVALID'; +export const ARGUMENT_OUT_OF_RANGE = 'GENERIC.ARGUMENT_OUT_OF_RANGE'; +export const ARGUMENT_NOT_PROVIDED = 'GENERIC.ARGUMENT_NOT_PROVIDED'; +export const NOT_FOUND = 'GENERIC.NOT_FOUND'; +export const CONFLICT = 'GENERIC.CONFLICT'; +export const INTERNAL_SERVER_ERROR = 'GENERIC.INTERNAL_SERVER_ERROR'; +export const DATABASE_ERROR = 'GENERIC.DATABASE_ERROR'; diff --git a/src/libs/exceptions/exceptions.ts b/src/libs/exceptions/exceptions.ts new file mode 100644 index 0000000..ceda019 --- /dev/null +++ b/src/libs/exceptions/exceptions.ts @@ -0,0 +1,99 @@ +import { + ARGUMENT_INVALID, + ARGUMENT_NOT_PROVIDED, + ARGUMENT_OUT_OF_RANGE, + CONFLICT, + DATABASE_ERROR, + INTERNAL_SERVER_ERROR, + NOT_FOUND, +} from '.'; +import { ExceptionBase } from './exception.base'; + +/** + * Used to indicate that an incorrect argument was provided to a method/function/class constructor + * + * @class ArgumentInvalidException + * @extends {ExceptionBase} + */ +export class ArgumentInvalidException extends ExceptionBase { + readonly code = ARGUMENT_INVALID; +} + +/** + * Used to indicate that an argument was not provided (is empty object/array, null of undefined). + * + * @class ArgumentNotProvidedException + * @extends {ExceptionBase} + */ +export class ArgumentNotProvidedException extends ExceptionBase { + readonly code = ARGUMENT_NOT_PROVIDED; +} + +/** + * Used to indicate that an argument is out of allowed range + * (for example: incorrect string/array length, number not in allowed min/max range etc) + * + * @class ArgumentOutOfRangeException + * @extends {ExceptionBase} + */ +export class ArgumentOutOfRangeException extends ExceptionBase { + readonly code = ARGUMENT_OUT_OF_RANGE; +} + +/** + * Used to indicate conflicting entities (usually in the database) + * + * @class ConflictException + * @extends {ExceptionBase} + */ +export class ConflictException extends ExceptionBase { + readonly code = CONFLICT; +} + +/** + * Used to indicate that entity is not found + * + * @class NotFoundException + * @extends {ExceptionBase} + */ +export class NotFoundException extends ExceptionBase { + static readonly message = 'Not found'; + + constructor(message = NotFoundException.message) { + super(message); + } + + readonly code = NOT_FOUND; +} + +/** + * Used to indicate an internal server error that does not fall under all other errors + * + * @class InternalServerErrorException + * @extends {ExceptionBase} + */ +export class InternalServerErrorException extends ExceptionBase { + static readonly message = 'Internal server error'; + + constructor(message = InternalServerErrorException.message) { + super(message); + } + + readonly code = INTERNAL_SERVER_ERROR; +} + +/** + * Used to indicate a database error + * + * @class DatabaseErrorException + * @extends {ExceptionBase} + */ +export class DatabaseErrorException extends ExceptionBase { + static readonly message = 'Database error'; + + constructor(message = DatabaseErrorException.message) { + super(message); + } + + readonly code = DATABASE_ERROR; +} diff --git a/src/libs/exceptions/index.ts b/src/libs/exceptions/index.ts new file mode 100644 index 0000000..4445442 --- /dev/null +++ b/src/libs/exceptions/index.ts @@ -0,0 +1,4 @@ +export * from './exception.base'; +export * from './exception.codes'; +export * from './exceptions'; +export * from './rpc-exception.codes.enum'; diff --git a/src/libs/exceptions/rpc-exception.codes.enum.ts b/src/libs/exceptions/rpc-exception.codes.enum.ts new file mode 100644 index 0000000..034391c --- /dev/null +++ b/src/libs/exceptions/rpc-exception.codes.enum.ts @@ -0,0 +1,19 @@ +export enum RpcExceptionCode { + OK = 0, + CANCELLED = 1, + UNKNOWN = 2, + INVALID_ARGUMENT = 3, + DEADLINE_EXCEEDED = 4, + NOT_FOUND = 5, + ALREADY_EXISTS = 6, + PERMISSION_DENIED = 7, + RESOURCE_EXHAUSTED = 8, + FAILED_PRECONDITION = 9, + ABORTED = 10, + OUT_OF_RANGE = 11, + UNIMPLEMENTED = 12, + INTERNAL = 13, + UNAVAILABLE = 14, + DATA_LOSS = 15, + UNAUTHENTICATED = 16, +} diff --git a/src/libs/guard.ts b/src/libs/guard.ts new file mode 100644 index 0000000..31b7a2b --- /dev/null +++ b/src/libs/guard.ts @@ -0,0 +1,55 @@ +export class Guard { + /** + * Checks if value is empty. Accepts strings, numbers, booleans, objects and arrays. + */ + static isEmpty(value: unknown): boolean { + if (typeof value === 'number' || typeof value === 'boolean') { + return false; + } + if (typeof value === 'undefined' || value === null) { + return true; + } + if (value instanceof Date) { + return false; + } + if (value instanceof Object && !Object.keys(value).length) { + return true; + } + if (Array.isArray(value)) { + if (value.length === 0) { + return true; + } + if (value.every((item) => Guard.isEmpty(item))) { + return true; + } + } + if (value === '') { + return true; + } + + return false; + } + + /** + * Checks length range of a provided number/string/array + */ + static lengthIsBetween( + value: number | string | Array, + min: number, + max: number, + ): boolean { + if (Guard.isEmpty(value)) { + throw new Error( + 'Cannot check length of a value. Provided value is empty', + ); + } + const valueLength = + typeof value === 'number' + ? Number(value).toString().length + : value.length; + if (valueLength >= min && valueLength <= max) { + return true; + } + return false; + } +} diff --git a/src/libs/ports/logger.port.ts b/src/libs/ports/logger.port.ts new file mode 100644 index 0000000..43bf996 --- /dev/null +++ b/src/libs/ports/logger.port.ts @@ -0,0 +1,6 @@ +export interface LoggerPort { + log(message: string, ...meta: unknown[]): void; + error(message: string, trace?: unknown, ...meta: unknown[]): void; + warn(message: string, ...meta: unknown[]): void; + debug(message: string, ...meta: unknown[]): void; +} diff --git a/src/interfaces/message-publisher.ts b/src/libs/ports/message-publisher.port.ts similarity index 58% rename from src/interfaces/message-publisher.ts rename to src/libs/ports/message-publisher.port.ts index 29ad456..fd35c90 100644 --- a/src/interfaces/message-publisher.ts +++ b/src/libs/ports/message-publisher.port.ts @@ -1,3 +1,3 @@ -export interface IPublishMessage { +export interface MessagePublisherPort { publish(routingKey: string, message: string): void; } diff --git a/src/libs/ports/prisma-repository.port.ts b/src/libs/ports/prisma-repository.port.ts new file mode 100644 index 0000000..79b86fe --- /dev/null +++ b/src/libs/ports/prisma-repository.port.ts @@ -0,0 +1,11 @@ +export interface PrismaRepositoryPort { + findUnique(options: any): Promise; + create(entity: any): Promise; +} + +export interface PrismaRawRepositoryPort { + $queryRaw( + query: TemplateStringsArray, + ...values: any[] + ): Promise; +} diff --git a/src/libs/tests/unit/db/prisma-repository.base.spec.ts b/src/libs/tests/unit/db/prisma-repository.base.spec.ts new file mode 100644 index 0000000..e3cc272 --- /dev/null +++ b/src/libs/tests/unit/db/prisma-repository.base.spec.ts @@ -0,0 +1,287 @@ +import { ResponseBase } from '@libs/api/response.base'; +import { PrismaRepositoryBase } from '@libs/db/prisma-repository.base'; +import { PrismaService } from '@libs/db/prisma.service'; +import { AggregateID, AggregateRoot, Mapper, RepositoryPort } from '@libs/ddd'; +import { + ConflictException, + DatabaseErrorException, + NotFoundException, +} from '@libs/exceptions'; +import { Injectable, Logger } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { Test, TestingModule } from '@nestjs/testing'; +import { Prisma } from '@prisma/client'; +import { v4 } from 'uuid'; + +interface FakeProps { + name: string; +} + +interface CreateFakeProps { + name: string; +} + +class FakeEntity extends AggregateRoot { + protected readonly _id: AggregateID; + + static create = (create: CreateFakeProps): FakeEntity => { + const id = v4(); + const props: FakeProps = { ...create }; + const fake = new FakeEntity({ id, props }); + return fake; + }; + + validate(): void { + // not implemented + } +} + +type FakeModel = { + uuid: string; + name: string; + createdAt: Date; + updatedAt: Date; +}; + +type FakeRepositoryPort = RepositoryPort; + +class FakeResponseDto extends ResponseBase { + name: string; +} + +const fakeRecord: FakeModel = { + uuid: 'd567ea3b-4981-43c9-9449-a409b5fa9fed', + name: 'fakeName', + createdAt: new Date('2023-06-28T16:02:00Z'), + updatedAt: new Date('2023-06-28T16:02:00Z'), +}; + +let recordId = 2; +const recordUuid = 'uuid-'; +const recordName = 'fakeName-'; + +const createRandomRecord = (): FakeModel => { + const fakeRecord: FakeModel = { + uuid: `${recordUuid}${recordId}`, + name: `${recordName}${recordId}`, + createdAt: new Date('2023-06-30T08:00:00Z'), + updatedAt: new Date('2023-06-30T08:00:00Z'), + }; + + recordId++; + + return fakeRecord; +}; + +const fakeRecords: FakeModel[] = []; +Array.from({ length: 10 }).forEach(() => { + fakeRecords.push(createRandomRecord()); +}); + +@Injectable() +class FakeMapper + implements Mapper +{ + toPersistence = (entity: FakeEntity): FakeModel => { + const copy = entity.getProps(); + const record: FakeModel = { + uuid: copy.id, + name: copy.name, + createdAt: copy.createdAt, + updatedAt: copy.updatedAt, + }; + return record; + }; + + toDomain = (record: FakeModel): FakeEntity => { + const entity = new FakeEntity({ + id: record.uuid, + createdAt: new Date(record.createdAt), + updatedAt: new Date(record.updatedAt), + props: { + name: record.name, + }, + }); + return entity; + }; + + toResponse = (entity: FakeEntity): FakeResponseDto => { + const props = entity.getProps(); + const response = new FakeResponseDto(entity); + response.name = props.name; + return response; + }; +} + +@Injectable() +class FakePrismaService extends PrismaService { + fake: any; +} + +const mockPrismaService = { + $queryRaw: jest + .fn() + .mockImplementationOnce(() => { + return true; + }) + .mockImplementation(() => { + throw new Prisma.PrismaClientKnownRequestError('Database unavailable', { + code: 'code', + clientVersion: 'version', + }); + }) + .mockImplementationOnce(() => { + throw new Error(); + }), + fake: { + create: jest + .fn() + .mockResolvedValueOnce(fakeRecord) + .mockImplementationOnce(() => { + throw new Prisma.PrismaClientKnownRequestError('Already exists', { + code: 'code', + clientVersion: 'version', + }); + }) + .mockImplementationOnce(() => { + throw new Error('An unknown error'); + }), + + findUnique: jest.fn().mockImplementation(async (params?: any) => { + let record: FakeModel; + + if (params?.where?.uuid) { + record = fakeRecords.find( + (record) => record.uuid === params?.where?.uuid, + ); + } + + if (!record && params?.where?.uuid == 'uuid-triggering-error') { + throw new Prisma.PrismaClientKnownRequestError('unknown request', { + code: 'code', + clientVersion: 'version', + }); + } + + return record; + }), + }, +}; + +@Injectable() +class FakeRepository + extends PrismaRepositoryBase + implements FakeRepositoryPort +{ + constructor( + prisma: FakePrismaService, + mapper: FakeMapper, + eventEmitter: EventEmitter2, + ) { + super( + prisma.fake, + prisma, + mapper, + eventEmitter, + new Logger(FakeRepository.name), + ); + } +} + +describe('PrismaRepositoryBase', () => { + let fakeRepository: FakeRepository; + let prisma: FakePrismaService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + EventEmitter2, + FakeRepository, + FakeMapper, + { + provide: FakePrismaService, + useValue: mockPrismaService, + }, + ], + }).compile(); + + fakeRepository = module.get(FakeRepository); + prisma = module.get(FakePrismaService); + }); + + it('should be defined', () => { + expect(fakeRepository).toBeDefined(); + expect(prisma).toBeDefined(); + }); + + describe('insert', () => { + it('should create a record', async () => { + jest.spyOn(prisma.fake, 'create'); + + await fakeRepository.insert( + FakeEntity.create({ + name: 'someFakeName', + }), + ); + expect(prisma.fake.create).toHaveBeenCalledTimes(1); + }); + + it('should throw a ConflictException if record already exists', async () => { + await expect( + fakeRepository.insert( + FakeEntity.create({ + name: 'someFakeName', + }), + ), + ).rejects.toBeInstanceOf(ConflictException); + }); + + it('should throw an Error if an error occurs', async () => { + await expect( + fakeRepository.insert( + FakeEntity.create({ + name: 'someFakeName', + }), + ), + ).rejects.toBeInstanceOf(Error); + }); + }); + + describe('findOneById', () => { + it('should find a record by its id', async () => { + const record = await fakeRepository.findOneById('uuid-3'); + expect(record.getProps().name).toBe('fakeName-3'); + }); + + it('should throw an Error for client error', async () => { + await expect( + fakeRepository.findOneById('uuid-triggering-error'), + ).rejects.toBeInstanceOf(Error); + }); + + it('should throw a NotFoundException if id is not found', async () => { + await expect( + fakeRepository.findOneById('wrong-id'), + ).rejects.toBeInstanceOf(NotFoundException); + }); + }); + + describe('healthCheck', () => { + it('should return a healthy result', async () => { + const res = await fakeRepository.healthCheck(); + expect(res).toBeTruthy(); + }); + + it('should throw an exception if database is not available', async () => { + await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf( + DatabaseErrorException, + ); + }); + + it('should throw a DatabaseErrorException if an error occurs', async () => { + await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf( + DatabaseErrorException, + ); + }); + }); +}); diff --git a/src/libs/tests/unit/ddd/aggregate-root.base.spec.ts b/src/libs/tests/unit/ddd/aggregate-root.base.spec.ts new file mode 100644 index 0000000..1b10ec6 --- /dev/null +++ b/src/libs/tests/unit/ddd/aggregate-root.base.spec.ts @@ -0,0 +1,76 @@ +import { + AggregateID, + AggregateRoot, + DomainEvent, + DomainEventProps, +} from '@libs/ddd'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { v4 } from 'uuid'; + +interface FakeProps { + name: string; +} + +interface CreateFakeProps { + name: string; +} + +class FakeRecordCreatedDomainEvent extends DomainEvent { + readonly name: string; + + constructor(props: DomainEventProps) { + super(props); + this.name = props.name; + } +} + +class FakeEntity extends AggregateRoot { + protected readonly _id: AggregateID; + + static create = (create: CreateFakeProps): FakeEntity => { + const id = v4(); + const props: FakeProps = { ...create }; + const fake = new FakeEntity({ id, props }); + fake.addEvent( + new FakeRecordCreatedDomainEvent({ + aggregateId: id, + name: props.name, + }), + ); + return fake; + }; + + validate(): void { + // not implemented + } +} + +const mockLogger = { + debug: jest.fn(), + log: jest.fn(), + error: jest.fn(), + warn: jest.fn(), +}; + +describe('AggregateRoot Base', () => { + it('should define an aggregate root based object instance', () => { + const fakeInstance = FakeEntity.create({ + name: 'someFakeName', + }); + expect(fakeInstance).toBeDefined(); + expect(fakeInstance.domainEvents.length).toBe(1); + }); + + it('should publish domain events', async () => { + jest.spyOn(mockLogger, 'debug'); + const eventEmitter = new EventEmitter2(); + jest.spyOn(eventEmitter, 'emitAsync'); + const fakeInstance = FakeEntity.create({ + name: 'someFakeName', + }); + await fakeInstance.publishEvents(mockLogger, eventEmitter); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(eventEmitter.emitAsync).toHaveBeenCalledTimes(1); + expect(fakeInstance.domainEvents.length).toBe(0); + }); +}); diff --git a/src/libs/tests/unit/ddd/command.base.spec.ts b/src/libs/tests/unit/ddd/command.base.spec.ts new file mode 100644 index 0000000..5ea8f42 --- /dev/null +++ b/src/libs/tests/unit/ddd/command.base.spec.ts @@ -0,0 +1,47 @@ +import { Command, CommandProps } from '@libs/ddd'; +import { ArgumentNotProvidedException } from '@libs/exceptions'; + +class FakeCommand extends Command { + readonly name: string; + + constructor(props: CommandProps) { + super(props); + this.name = props.name; + } +} + +class BadFakeCommand extends Command { + constructor(props: CommandProps) { + super(props); + } +} + +describe('Command Base', () => { + it('should define a command based object instance', () => { + const fakeCommand = new FakeCommand({ name: 'fakeName' }); + expect(fakeCommand).toBeDefined(); + expect(fakeCommand.id.length).toBe(36); + }); + + it('should define a command based object instance with a provided id', () => { + const fakeCommand = new FakeCommand({ id: 'some-id', name: 'fakeName' }); + expect(fakeCommand.id).toBe('some-id'); + }); + + it('should define a command based object instance with metadata', () => { + const fakeCommand = new FakeCommand({ + name: 'fakeName', + metadata: { + correlationId: 'some-correlation-id', + causationId: 'some-causation-id', + userId: 'some-user-id', + timestamp: new Date('2023-06-28T05:00:00Z').getTime(), + }, + }); + expect(fakeCommand.metadata.timestamp).toBe(1687928400000); + }); + + it('should throw an exception if props are empty', () => { + expect(() => new BadFakeCommand({})).toThrow(ArgumentNotProvidedException); + }); +}); diff --git a/src/libs/tests/unit/ddd/domain-event.base.spec.ts b/src/libs/tests/unit/ddd/domain-event.base.spec.ts new file mode 100644 index 0000000..d4751b2 --- /dev/null +++ b/src/libs/tests/unit/ddd/domain-event.base.spec.ts @@ -0,0 +1,42 @@ +import { DomainEvent, DomainEventProps } from '@libs/ddd'; +import { ArgumentNotProvidedException } from '@libs/exceptions'; + +class FakeDomainEvent extends DomainEvent { + readonly name: string; + + constructor(props: DomainEventProps) { + super(props); + this.name = props.name; + } +} + +describe('DomainEvent Base', () => { + it('should define a domain event based object instance', () => { + const fakeDomainEvent = new FakeDomainEvent({ + aggregateId: 'some-id', + name: 'some-name', + }); + expect(fakeDomainEvent).toBeDefined(); + expect(fakeDomainEvent.id.length).toBe(36); + }); + + it('should define a domain event based object instance with metadata', () => { + const fakeDomainEvent = new FakeDomainEvent({ + aggregateId: 'some-id', + name: 'some-name', + metadata: { + correlationId: 'some-correlation-id', + causationId: 'some-causation-id', + userId: 'some-user-id', + timestamp: new Date('2023-06-28T05:00:00Z').getTime(), + }, + }); + expect(fakeDomainEvent.metadata.timestamp).toBe(1687928400000); + }); + it('should throw an exception if props are empty', () => { + const emptyProps: DomainEventProps = undefined; + expect(() => new FakeDomainEvent(emptyProps)).toThrow( + ArgumentNotProvidedException, + ); + }); +}); diff --git a/src/libs/tests/unit/ddd/entity.base.spec.ts b/src/libs/tests/unit/ddd/entity.base.spec.ts new file mode 100644 index 0000000..38f90f2 --- /dev/null +++ b/src/libs/tests/unit/ddd/entity.base.spec.ts @@ -0,0 +1,209 @@ +import { Entity } from '@libs/ddd'; +import { ArgumentOutOfRangeException } from '@libs/exceptions'; + +interface FakeProps { + name: string; +} + +class FakeEntity extends Entity { + protected _id: string; + + validate(): void { + // not implemented + } +} + +describe('Entity Base', () => { + it('should define an entity based object instance', () => { + const fakeInstance = new FakeEntity({ + id: 'some-id', + props: { + name: 'some-name', + }, + }); + expect(fakeInstance).toBeDefined(); + expect(fakeInstance.id).toBe('some-id'); + expect(fakeInstance.createdAt).toBeInstanceOf(Date); + expect(fakeInstance.updatedAt).toBeInstanceOf(Date); + expect(FakeEntity.isEntity(fakeInstance)).toBeTruthy(); + }); + + it('should define an entity with given created and updated dates', () => { + const fakeInstance = new FakeEntity({ + id: 'some-id', + createdAt: new Date('2023-06-28T05:00:00Z'), + updatedAt: new Date('2023-06-28T06:00:00Z'), + props: { + name: 'some-name', + }, + }); + expect(fakeInstance.createdAt.getUTCHours()).toBe(5); + expect(fakeInstance.updatedAt.getUTCHours()).toBe(6); + }); + + it('should compare entities', () => { + const fakeInstance = new FakeEntity({ + id: 'some-id', + props: { + name: 'some-name', + }, + }); + const fakeInstanceClone = new FakeEntity({ + id: 'some-id', + props: { + name: 'some-name', + }, + }); + const fakeInstanceNotReallyClone = new FakeEntity({ + id: 'some-slightly-different-id', + props: { + name: 'some-name', + }, + }); + const undefinedFakeInstance: FakeEntity = undefined; + expect(fakeInstance.equals(undefinedFakeInstance)).toBeFalsy(); + expect(fakeInstance.equals(fakeInstance)).toBeTruthy(); + expect(fakeInstance.equals(fakeInstanceClone)).toBeTruthy(); + expect(fakeInstance.equals(fakeInstanceNotReallyClone)).toBeFalsy(); + }); + + it('should convert entity to plain object', () => { + const fakeInstance = new FakeEntity({ + id: 'some-id', + createdAt: new Date('2023-06-28T05:00:00Z'), + updatedAt: new Date('2023-06-28T06:00:00Z'), + props: { + name: 'some-name', + }, + }); + expect(fakeInstance.toObject()).toEqual({ + id: 'some-id', + createdAt: new Date('2023-06-28T05:00:00.000Z'), + updatedAt: new Date('2023-06-28T06:00:00.000Z'), + name: 'some-name', + }); + }); + + it('should throw an exception if props number is too high', () => { + interface BigFakeProps { + prop1: string; + prop2: string; + prop3: string; + prop4: string; + prop5: string; + prop6: string; + prop7: string; + prop8: string; + prop9: string; + prop10: string; + prop11: string; + prop12: string; + prop13: string; + prop14: string; + prop15: string; + prop16: string; + prop17: string; + prop18: string; + prop19: string; + prop20: string; + prop21: string; + prop22: string; + prop23: string; + prop24: string; + prop25: string; + prop26: string; + prop27: string; + prop28: string; + prop29: string; + prop30: string; + prop31: string; + prop32: string; + prop33: string; + prop34: string; + prop35: string; + prop36: string; + prop37: string; + prop38: string; + prop39: string; + prop40: string; + prop41: string; + prop42: string; + prop43: string; + prop44: string; + prop45: string; + prop46: string; + prop47: string; + prop48: string; + prop49: string; + prop50: string; + prop51: string; + } + + class BigFakeEntity extends Entity { + protected _id: string; + + validate(): void { + // not implemented + } + } + expect( + () => + new BigFakeEntity({ + id: 'some-id', + props: { + prop1: 'some-name', + prop2: 'some-name', + prop3: 'some-name', + prop4: 'some-name', + prop5: 'some-name', + prop6: 'some-name', + prop7: 'some-name', + prop8: 'some-name', + prop9: 'some-name', + prop10: 'some-name', + prop11: 'some-name', + prop12: 'some-name', + prop13: 'some-name', + prop14: 'some-name', + prop15: 'some-name', + prop16: 'some-name', + prop17: 'some-name', + prop18: 'some-name', + prop19: 'some-name', + prop20: 'some-name', + prop21: 'some-name', + prop22: 'some-name', + prop23: 'some-name', + prop24: 'some-name', + prop25: 'some-name', + prop26: 'some-name', + prop27: 'some-name', + prop28: 'some-name', + prop29: 'some-name', + prop30: 'some-name', + prop31: 'some-name', + prop32: 'some-name', + prop33: 'some-name', + prop34: 'some-name', + prop35: 'some-name', + prop36: 'some-name', + prop37: 'some-name', + prop38: 'some-name', + prop39: 'some-name', + prop40: 'some-name', + prop41: 'some-name', + prop42: 'some-name', + prop43: 'some-name', + prop44: 'some-name', + prop45: 'some-name', + prop46: 'some-name', + prop47: 'some-name', + prop48: 'some-name', + prop49: 'some-name', + prop50: 'some-name', + prop51: 'some-name', + }, + }), + ).toThrow(ArgumentOutOfRangeException); + }); +}); diff --git a/src/libs/tests/unit/ddd/query.base.spec.ts b/src/libs/tests/unit/ddd/query.base.spec.ts new file mode 100644 index 0000000..9d93d8f --- /dev/null +++ b/src/libs/tests/unit/ddd/query.base.spec.ts @@ -0,0 +1,40 @@ +import { + PaginatedParams, + PaginatedQueryBase, + QueryBase, +} from '@libs/ddd/query.base'; + +class FakeQuery extends QueryBase { + readonly id: string; + + constructor(id: string) { + super(); + this.id = id; + } +} + +describe('Query Base', () => { + it('should define a query based object instance', () => { + const fakeQuery = new FakeQuery('some-id'); + expect(fakeQuery).toBeDefined(); + }); +}); + +class FakePaginatedQuery extends PaginatedQueryBase { + readonly id: string; + + constructor(props: PaginatedParams) { + super(props); + this.id = props.id; + } +} + +describe('Paginated Query Base', () => { + it('should define a paginated query based object instance', () => { + const fakePaginatedQuery = new FakePaginatedQuery({ + id: 'some-id', + page: 1, + }); + expect(fakePaginatedQuery).toBeDefined(); + }); +}); diff --git a/src/libs/tests/unit/ddd/value-object.base.spec.ts b/src/libs/tests/unit/ddd/value-object.base.spec.ts new file mode 100644 index 0000000..2b99005 --- /dev/null +++ b/src/libs/tests/unit/ddd/value-object.base.spec.ts @@ -0,0 +1,42 @@ +import { ValueObject } from '@libs/ddd'; + +interface FakeProps { + name: string; +} + +class FakeValueObject extends ValueObject { + get name(): string { + return this.props.name; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected validate(props: FakeProps): void { + return; + } +} + +describe('Value Object Base', () => { + it('should create a base value object', () => { + const fakeValueObject = new FakeValueObject({ name: 'fakeName' }); + expect(fakeValueObject).toBeDefined(); + expect(ValueObject.isValueObject(fakeValueObject)).toBeTruthy(); + }); + + it('should compare value objects', () => { + const fakeValueObject = new FakeValueObject({ name: 'fakeName' }); + const fakeValueObjectClone = new FakeValueObject({ name: 'fakeName' }); + const undefinedFakeValueObject: FakeValueObject = undefined; + const nullFakeValueObject: FakeValueObject = null; + expect(fakeValueObject.equals(undefinedFakeValueObject)).toBeFalsy(); + expect(fakeValueObject.equals(nullFakeValueObject)).toBeFalsy(); + expect(fakeValueObject.equals(fakeValueObject)).toBeTruthy(); + expect(fakeValueObject.equals(fakeValueObjectClone)).toBeTruthy(); + }); + + it('should unpack value object props', () => { + const fakeValueObject = new FakeValueObject({ name: 'fakeName' }); + expect(fakeValueObject.unpack()).toEqual({ + name: 'fakeName', + }); + }); +}); diff --git a/src/libs/tests/unit/guard.spec.ts b/src/libs/tests/unit/guard.spec.ts new file mode 100644 index 0000000..8a4b543 --- /dev/null +++ b/src/libs/tests/unit/guard.spec.ts @@ -0,0 +1,65 @@ +import { Guard } from '@libs/guard'; + +describe('Guard', () => { + describe('isEmpty', () => { + it('should return false for a number', () => { + expect(Guard.isEmpty(1)).toBeFalsy(); + }); + it('should return false for a falsy boolean', () => { + expect(Guard.isEmpty(false)).toBeFalsy(); + }); + it('should return false for a truthy boolean', () => { + expect(Guard.isEmpty(true)).toBeFalsy(); + }); + it('should return true for undefined', () => { + expect(Guard.isEmpty(undefined)).toBeTruthy(); + }); + it('should return true for null', () => { + expect(Guard.isEmpty(null)).toBeTruthy(); + }); + it('should return false for a Date', () => { + expect(Guard.isEmpty(new Date('2023-06-28'))).toBeFalsy(); + }); + it('should return false for an object with keys', () => { + expect(Guard.isEmpty({ key: 'value' })).toBeFalsy(); + }); + it('should return true for an object without keys', () => { + expect(Guard.isEmpty({})).toBeTruthy(); + }); + it('should return true for an array without values', () => { + expect(Guard.isEmpty([])).toBeTruthy(); + }); + it('should return true for an array with only empty values', () => { + expect(Guard.isEmpty([null, undefined])).toBeTruthy(); + }); + it('should return false for an array with some empty values', () => { + expect(Guard.isEmpty([1, null, undefined])).toBeFalsy(); + }); + it('should return true for an empty string', () => { + expect(Guard.isEmpty('')).toBeTruthy(); + }); + }); + describe('lengthIsBetween', () => { + it('should return true for a string in the range', () => { + expect(Guard.lengthIsBetween('test', 0, 4)).toBeTruthy(); + }); + it('should return true for a number in the range', () => { + expect(Guard.lengthIsBetween(2, 0, 4)).toBeTruthy(); + }); + it('should return true for an array with number of elements in the range', () => { + expect(Guard.lengthIsBetween([1, 2, 3], 0, 4)).toBeTruthy(); + }); + it('should return false for a string not in the range', () => { + expect(Guard.lengthIsBetween('test', 5, 9)).toBeFalsy(); + }); + it('should return false for a number not in the range', () => { + expect(Guard.lengthIsBetween(2, 3, 6)).toBeFalsy(); + }); + it('should return false for an array with number of elements not in the range', () => { + expect(Guard.lengthIsBetween([1, 2, 3], 10, 12)).toBeFalsy(); + }); + it('should throw an exception if value is empty', () => { + expect(() => Guard.lengthIsBetween(undefined, 0, 4)).toThrow(); + }); + }); +}); diff --git a/src/utils/tests/unit/rpc-validation-pipe.usecase.spec.ts b/src/libs/tests/unit/utils/rpc-validation-pipe.usecase.spec.ts similarity index 58% rename from src/utils/tests/unit/rpc-validation-pipe.usecase.spec.ts rename to src/libs/tests/unit/utils/rpc-validation-pipe.usecase.spec.ts index 5b47172..fffdad4 100644 --- a/src/utils/tests/unit/rpc-validation-pipe.usecase.spec.ts +++ b/src/libs/tests/unit/utils/rpc-validation-pipe.usecase.spec.ts @@ -1,6 +1,6 @@ import { ArgumentMetadata } from '@nestjs/common'; -import { RpcValidationPipe } from '../../pipes/rpc.validation-pipe'; -import { FindAdByUuidRequest } from '../../../modules/ad/domain/dtos/find-ad-by-uuid.request'; +import { FindAdByIdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/find-ad-by-id.request.dto'; +import { RpcValidationPipe } from '@libs/utils/pipes/rpc.validation-pipe'; describe('RpcValidationPipe', () => { it('should not validate request', async () => { @@ -10,10 +10,10 @@ describe('RpcValidationPipe', () => { }); const metadata: ArgumentMetadata = { type: 'body', - metatype: FindAdByUuidRequest, + metatype: FindAdByIdRequestDto, data: '', }; - await target.transform({}, metadata).catch((err) => { + await target.transform({}, metadata).catch((err) => { expect(err.message).toEqual('Rpc Exception'); }); }); diff --git a/src/libs/types/index.ts b/src/libs/types/index.ts new file mode 100644 index 0000000..da248df --- /dev/null +++ b/src/libs/types/index.ts @@ -0,0 +1,6 @@ +/** Consider creating a bunch of shared custom utility + * types for different situations. + * Alternatively you can use a library like + * https://github.com/andnp/SimplyTyped + */ +export * from './object-literal.type'; diff --git a/src/libs/types/object-literal.type.ts b/src/libs/types/object-literal.type.ts new file mode 100644 index 0000000..c8d50ec --- /dev/null +++ b/src/libs/types/object-literal.type.ts @@ -0,0 +1,6 @@ +/** + * Interface of the simple literal object with any string keys. + */ +export interface ObjectLiteral { + [key: string]: unknown; +} diff --git a/src/libs/utils/convert-props-to-object.util.ts b/src/libs/utils/convert-props-to-object.util.ts new file mode 100644 index 0000000..4605fc7 --- /dev/null +++ b/src/libs/utils/convert-props-to-object.util.ts @@ -0,0 +1,47 @@ +/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types */ +import { Entity } from '../ddd/entity.base'; +import { ValueObject } from '../ddd/value-object.base'; + +function isEntity(obj: unknown): obj is Entity { + /** + * 'instanceof Entity' causes error here for some reason. + * Probably creates some circular dependency. This is a workaround + * until I find a solution :) + */ + return ( + Object.prototype.hasOwnProperty.call(obj, 'toObject') && + Object.prototype.hasOwnProperty.call(obj, 'id') && + ValueObject.isValueObject((obj as Entity).id) + ); +} + +function convertToPlainObject(item: any): any { + if (ValueObject.isValueObject(item)) { + return item.unpack(); + } + if (isEntity(item)) { + return item.toObject(); + } + return item; +} + +/** + * Converts Entity/Value Objects props to a plain object. + * Useful for testing and debugging. + * @param props + */ +export function convertPropsToObject(props: any): any { + const propsCopy = structuredClone(props); + + // eslint-disable-next-line guard-for-in + for (const prop in propsCopy) { + if (Array.isArray(propsCopy[prop])) { + propsCopy[prop] = (propsCopy[prop] as Array).map((item) => { + return convertToPlainObject(item); + }); + } + propsCopy[prop] = convertToPlainObject(propsCopy[prop]); + } + + return propsCopy; +} diff --git a/src/libs/utils/index.ts b/src/libs/utils/index.ts new file mode 100644 index 0000000..546588f --- /dev/null +++ b/src/libs/utils/index.ts @@ -0,0 +1 @@ +export * from './convert-props-to-object.util'; diff --git a/src/utils/pipes/rpc.validation-pipe.ts b/src/libs/utils/pipes/rpc.validation-pipe.ts similarity index 74% rename from src/utils/pipes/rpc.validation-pipe.ts rename to src/libs/utils/pipes/rpc.validation-pipe.ts index f2b8c19..75b1852 100644 --- a/src/utils/pipes/rpc.validation-pipe.ts +++ b/src/libs/utils/pipes/rpc.validation-pipe.ts @@ -1,3 +1,4 @@ +import { RpcExceptionCode } from '@libs/exceptions/rpc-exception.codes.enum'; import { Injectable, ValidationPipe } from '@nestjs/common'; import { RpcException } from '@nestjs/microservices'; @@ -6,7 +7,7 @@ export class RpcValidationPipe extends ValidationPipe { createExceptionFactory() { return (validationErrors = []) => { return new RpcException({ - code: 3, + code: RpcExceptionCode.INVALID_ARGUMENT, message: this.flattenValidationErrors(validationErrors), }); }; diff --git a/src/main.ts b/src/main.ts index 1303641..e12915e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -13,10 +13,13 @@ async function bootstrap() { options: { package: ['ad', 'health'], protoPath: [ - join(__dirname, 'modules/ad/adapters/primaries/ad.proto'), - join(__dirname, 'modules/health/adapters/primaries/health.proto'), + join(__dirname, 'modules/ad/interface/grpc-controllers/ad.proto'), + join( + __dirname, + 'modules/health/interface/grpc-controllers/health.proto', + ), ], - url: process.env.SERVICE_URL + ':' + process.env.SERVICE_PORT, + url: `${process.env.SERVICE_URL}:${process.env.SERVICE_PORT}`, loader: { keepCase: true }, }, }); diff --git a/src/modules/ad/ad.constants.ts b/src/modules/ad/ad.constants.ts deleted file mode 100644 index 08f6845..0000000 --- a/src/modules/ad/ad.constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const PARAMS_PROVIDER = Symbol(); diff --git a/src/modules/ad/ad.di-tokens.ts b/src/modules/ad/ad.di-tokens.ts new file mode 100644 index 0000000..5d06236 --- /dev/null +++ b/src/modules/ad/ad.di-tokens.ts @@ -0,0 +1,4 @@ +export const PARAMS_PROVIDER = Symbol('PARAMS_PROVIDER'); +export const TIMEZONE_FINDER = Symbol('TIMEZONE_FINDER'); +export const TIME_CONVERTER = Symbol('TIME_CONVERTER'); +export const AD_REPOSITORY = Symbol('AD_REPOSITORY'); diff --git a/src/modules/ad/ad.mapper.ts b/src/modules/ad/ad.mapper.ts new file mode 100644 index 0000000..61f75d4 --- /dev/null +++ b/src/modules/ad/ad.mapper.ts @@ -0,0 +1,247 @@ +import { Mapper } from '@libs/ddd'; +import { AdResponseDto } from './interface/dtos/ad.response.dto'; +import { Inject, Injectable } from '@nestjs/common'; +import { AdEntity } from './core/ad.entity'; +import { + AdWriteModel, + AdReadModel, + WaypointModel, +} from './infrastructure/ad.repository'; +import { Frequency } from './core/ad.types'; +import { WaypointProps } from './core/value-objects/waypoint.value-object'; +import { v4 } from 'uuid'; +import { + PARAMS_PROVIDER, + TIMEZONE_FINDER, + TIME_CONVERTER, +} from './ad.di-tokens'; +import { TimezoneFinderPort } from './core/ports/timezone-finder.port'; +import { DefaultParamsProviderPort } from './core/ports/default-params-provider.port'; +import { DefaultParams } from './core/ports/default-params.type'; +import { TimeConverterPort } from './core/ports/time-converter.port'; + +/** + * Mapper constructs objects that are used in different layers: + * Record is an object that is stored in a database, + * Entity is an object that is used in application domain layer, + * and a ResponseDTO is an object returned to a user (usually as json). + */ + +@Injectable() +export class AdMapper + implements Mapper +{ + private readonly _defaultParams: DefaultParams; + + constructor( + @Inject(PARAMS_PROVIDER) + private readonly defaultParamsProvider: DefaultParamsProviderPort, + @Inject(TIMEZONE_FINDER) + private readonly timezoneFinder: TimezoneFinderPort, + @Inject(TIME_CONVERTER) + private readonly timeConverter: TimeConverterPort, + ) { + this._defaultParams = defaultParamsProvider.getParams(); + } + + toPersistence = (entity: AdEntity): AdWriteModel => { + const copy = entity.getProps(); + const { lon, lat } = copy.waypoints[0].address.coordinates; + const timezone = this.timezoneFinder.timezones( + lon, + lat, + this._defaultParams.DEFAULT_TIMEZONE, + )[0]; + const now = new Date(); + const record: AdWriteModel = { + uuid: copy.id, + userUuid: copy.userId, + driver: copy.driver, + passenger: copy.passenger, + frequency: copy.frequency, + fromDate: new Date(copy.fromDate), + toDate: new Date(copy.toDate), + monTime: copy.schedule.mon + ? this.timeConverter.localDateTimeToUtc( + copy.fromDate, + copy.schedule.mon, + timezone, + ) + : undefined, + tueTime: copy.schedule.tue + ? this.timeConverter.localDateTimeToUtc( + copy.fromDate, + copy.schedule.tue, + timezone, + ) + : undefined, + wedTime: copy.schedule.wed + ? this.timeConverter.localDateTimeToUtc( + copy.fromDate, + copy.schedule.wed, + timezone, + ) + : undefined, + thuTime: copy.schedule.thu + ? this.timeConverter.localDateTimeToUtc( + copy.fromDate, + copy.schedule.thu, + timezone, + ) + : undefined, + friTime: copy.schedule.fri + ? this.timeConverter.localDateTimeToUtc( + copy.fromDate, + copy.schedule.fri, + timezone, + ) + : undefined, + satTime: copy.schedule.sat + ? this.timeConverter.localDateTimeToUtc( + copy.fromDate, + copy.schedule.sat, + timezone, + ) + : undefined, + sunTime: copy.schedule.sun + ? this.timeConverter.localDateTimeToUtc( + copy.fromDate, + copy.schedule.sun, + timezone, + ) + : undefined, + monMargin: copy.marginDurations.mon, + tueMargin: copy.marginDurations.tue, + wedMargin: copy.marginDurations.wed, + thuMargin: copy.marginDurations.thu, + friMargin: copy.marginDurations.fri, + satMargin: copy.marginDurations.sat, + sunMargin: copy.marginDurations.sun, + seatsProposed: copy.seatsProposed, + seatsRequested: copy.seatsRequested, + strict: copy.strict, + waypoints: { + create: copy.waypoints.map((waypoint: WaypointProps) => ({ + uuid: v4(), + position: waypoint.position, + name: waypoint.address.name, + houseNumber: waypoint.address.houseNumber, + street: waypoint.address.street, + locality: waypoint.address.locality, + postalCode: waypoint.address.postalCode, + country: waypoint.address.country, + lon: waypoint.address.coordinates.lon, + lat: waypoint.address.coordinates.lat, + createdAt: now, + updatedAt: now, + })), + }, + createdAt: copy.createdAt, + updatedAt: copy.updatedAt, + }; + return record; + }; + + toDomain = (record: AdReadModel): AdEntity => { + const timezone = this.timezoneFinder.timezones( + record.waypoints[0].lon, + record.waypoints[0].lat, + this._defaultParams.DEFAULT_TIMEZONE, + )[0]; + const entity = new AdEntity({ + id: record.uuid, + createdAt: new Date(record.createdAt), + updatedAt: new Date(record.updatedAt), + props: { + userId: record.userUuid, + driver: record.driver, + passenger: record.passenger, + frequency: Frequency[record.frequency], + fromDate: record.fromDate.toISOString().split('T')[0], + toDate: record.toDate.toISOString().split('T')[0], + schedule: { + mon: record.monTime?.toISOString(), + tue: record.tueTime?.toISOString(), + wed: record.wedTime + ? this.timeConverter.utcDatetimeToLocalTime( + record.wedTime.toISOString(), + timezone, + ) + : undefined, + thu: record.thuTime + ? this.timeConverter.utcDatetimeToLocalTime( + record.thuTime.toISOString(), + timezone, + ) + : undefined, + fri: record.friTime?.toISOString(), + sat: record.satTime?.toISOString(), + sun: record.sunTime?.toISOString(), + }, + marginDurations: { + mon: record.monMargin, + tue: record.tueMargin, + wed: record.wedMargin, + thu: record.thuMargin, + fri: record.friMargin, + sat: record.satMargin, + sun: record.sunMargin, + }, + seatsProposed: record.seatsProposed, + seatsRequested: record.seatsRequested, + strict: record.strict, + waypoints: record.waypoints.map((waypoint: WaypointModel) => ({ + position: waypoint.position, + address: { + name: waypoint.name, + houseNumber: waypoint.houseNumber, + street: waypoint.street, + postalCode: waypoint.postalCode, + locality: waypoint.locality, + country: waypoint.country, + coordinates: { + lon: waypoint.lon, + lat: waypoint.lat, + }, + }, + })), + }, + }); + return entity; + }; + + toResponse = (entity: AdEntity): AdResponseDto => { + const props = entity.getProps(); + const response = new AdResponseDto(entity); + response.userId = props.userId; + response.driver = props.driver; + response.passenger = props.passenger; + response.frequency = props.frequency; + response.fromDate = props.fromDate; + response.toDate = props.toDate; + response.schedule = { ...props.schedule }; + response.marginDurations = { ...props.marginDurations }; + response.seatsProposed = props.seatsProposed; + response.seatsRequested = props.seatsRequested; + response.waypoints = props.waypoints.map((waypoint: WaypointProps) => ({ + position: waypoint.position, + name: waypoint.address.name, + houseNumber: waypoint.address.houseNumber, + street: waypoint.address.street, + postalCode: waypoint.address.postalCode, + locality: waypoint.address.locality, + country: waypoint.address.country, + lon: waypoint.address.coordinates.lon, + lat: waypoint.address.coordinates.lat, + })); + return response; + }; + + /* ^ Data returned to the user is whitelisted to avoid leaks. + If a new property is added, like password or a + credit card number, it won't be returned + unless you specifically allow this. + (avoid blacklisting, which will return everything + but blacklisted items, which can lead to a data leak). + */ +} diff --git a/src/modules/ad/ad.module.ts b/src/modules/ad/ad.module.ts index a7e0218..5a5e3ce 100644 --- a/src/modules/ad/ad.module.ts +++ b/src/modules/ad/ad.module.ts @@ -1,37 +1,97 @@ -import { Module } from '@nestjs/common'; -import { AdController } from './adapters/primaries/ad.controller'; -import { DatabaseModule } from '../database/database.module'; +import { Module, Provider } from '@nestjs/common'; +import { CreateAdGrpcController } from './interface/grpc-controllers/create-ad.grpc.controller'; import { CqrsModule } from '@nestjs/cqrs'; -import { AdProfile } from './mappers/ad.profile'; -import { AdsRepository } from './adapters/secondaries/ads.repository'; -import { FindAdByUuidUseCase } from './domain/usecases/find-ad-by-uuid.usecase'; -import { CreateAdUseCase } from './domain/usecases/create-ad.usecase'; -import { DefaultParamsProvider } from './adapters/secondaries/default-params.provider'; -import { PARAMS_PROVIDER } from './ad.constants'; -import { MESSAGE_BROKER_PUBLISHER, MESSAGE_PUBLISHER } from 'src/app.constants'; +import { + AD_REPOSITORY, + PARAMS_PROVIDER, + TIMEZONE_FINDER, + TIME_CONVERTER, +} from './ad.di-tokens'; +import { + MESSAGE_BROKER_PUBLISHER, + MESSAGE_PUBLISHER, +} from '@src/app.constants'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; -import { MessagePublisher } from './adapters/secondaries/message-publisher'; +import { AdRepository } from './infrastructure/ad.repository'; +import { DefaultParamsProvider } from './infrastructure/default-params-provider'; +import { MessagePublisher } from './infrastructure/message-publisher'; +import { AdMapper } from './ad.mapper'; +import { CreateAdService } from './core/commands/create-ad/create-ad.service'; +import { TimezoneFinder } from './infrastructure/timezone-finder'; +import { PrismaService } from '@libs/db/prisma.service'; +import { TimeConverter } from './infrastructure/time-converter'; +import { FindAdByIdGrpcController } from './interface/grpc-controllers/find-ad-by-id.grpc.controller'; +import { FindAdByIdQueryHandler } from './core/queries/find-ad-by-id/find-ad-by-id.query-handler'; +import { PublishMessageWhenAdIsCreatedDomainEventHandler } from './core/event-handlers/publish-message-when-ad-is-created.domain-event-handler'; +import { PublishLogMessageWhenAdIsCreatedDomainEventHandler } from './core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler'; + +const grpcControllers = [CreateAdGrpcController, FindAdByIdGrpcController]; + +const eventHandlers: Provider[] = [ + PublishMessageWhenAdIsCreatedDomainEventHandler, + PublishLogMessageWhenAdIsCreatedDomainEventHandler, +]; + +const commandHandlers: Provider[] = [CreateAdService]; + +const queryHandlers: Provider[] = [FindAdByIdQueryHandler]; + +const mappers: Provider[] = [AdMapper]; + +const repositories: Provider[] = [ + { + provide: AD_REPOSITORY, + useClass: AdRepository, + }, +]; + +const messageBrokers: Provider[] = [ + { + provide: MESSAGE_BROKER_PUBLISHER, + useClass: MessageBrokerPublisher, + }, + { + provide: MESSAGE_PUBLISHER, + useClass: MessagePublisher, + }, +]; + +const orms: Provider[] = [PrismaService]; + +const utilities: Provider[] = [ + { + provide: PARAMS_PROVIDER, + useClass: DefaultParamsProvider, + }, + { + provide: TIMEZONE_FINDER, + useClass: TimezoneFinder, + }, + { + provide: TIME_CONVERTER, + useClass: TimeConverter, + }, +]; @Module({ - imports: [DatabaseModule, CqrsModule], - controllers: [AdController], + imports: [CqrsModule], + controllers: [...grpcControllers], providers: [ - AdProfile, - AdsRepository, - FindAdByUuidUseCase, - CreateAdUseCase, - { - provide: PARAMS_PROVIDER, - useClass: DefaultParamsProvider, - }, - { - provide: MESSAGE_BROKER_PUBLISHER, - useClass: MessageBrokerPublisher, - }, - { - provide: MESSAGE_PUBLISHER, - useClass: MessagePublisher, - }, + ...eventHandlers, + ...commandHandlers, + ...queryHandlers, + ...mappers, + ...repositories, + ...messageBrokers, + ...orms, + ...utilities, + ], + exports: [ + PrismaService, + AdMapper, + AD_REPOSITORY, + PARAMS_PROVIDER, + TIMEZONE_FINDER, ], }) export class AdModule {} diff --git a/src/modules/ad/adapters/primaries/ad.controller.ts b/src/modules/ad/adapters/primaries/ad.controller.ts deleted file mode 100644 index b3aa278..0000000 --- a/src/modules/ad/adapters/primaries/ad.controller.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { Mapper } from '@automapper/core'; -import { InjectMapper } from '@automapper/nestjs'; -import { Controller, UsePipes } from '@nestjs/common'; -import { CommandBus, QueryBus } from '@nestjs/cqrs'; -import { GrpcMethod, RpcException } from '@nestjs/microservices'; -import { RpcValidationPipe } from '../../../../utils/pipes/rpc.validation-pipe'; -import { FindAdByUuidRequest } from '../../domain/dtos/find-ad-by-uuid.request'; -import { AdPresenter } from './ad.presenter'; -import { FindAdByUuidQuery } from '../../queries/find-ad-by-uuid.query'; -import { Ad } from '../../domain/entities/ad'; -import { CreateAdRequest } from '../../domain/dtos/create-ad.request'; -import { CreateAdCommand } from '../../commands/create-ad.command'; -import { DatabaseException } from '../../../database/exceptions/database.exception'; - -@UsePipes( - new RpcValidationPipe({ - whitelist: false, - forbidUnknownValues: false, - }), -) -@Controller() -export class AdController { - constructor( - private readonly commandBus: CommandBus, - private readonly queryBus: QueryBus, - @InjectMapper() private readonly mapper: Mapper, - ) {} - - @GrpcMethod('AdsService', 'FindOneByUuid') - async findOnebyUuid(data: FindAdByUuidRequest): Promise { - try { - const ad = await this.queryBus.execute(new FindAdByUuidQuery(data)); - return this.mapper.map(ad, Ad, AdPresenter); - } catch (e) { - throw new RpcException({ - code: e.code, - message: e.message, - }); - } - } - - @GrpcMethod('AdsService', 'Create') - async createAd(data: CreateAdRequest): Promise { - try { - const ad = await this.commandBus.execute(new CreateAdCommand(data)); - return this.mapper.map(ad, Ad, AdPresenter); - } catch (e) { - if (e instanceof DatabaseException) { - if (e.message.includes('Already exists')) { - throw new RpcException({ - code: 6, - message: 'Ad already exists', - }); - } - } - throw new RpcException({}); - } - } -} diff --git a/src/modules/ad/adapters/primaries/ad.presenter.ts b/src/modules/ad/adapters/primaries/ad.presenter.ts deleted file mode 100644 index 1a4d67c..0000000 --- a/src/modules/ad/adapters/primaries/ad.presenter.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { AutoMap } from '@automapper/classes'; - -export class AdPresenter { - @AutoMap() - uuid: string; -} diff --git a/src/modules/ad/adapters/primaries/ad.proto b/src/modules/ad/adapters/primaries/ad.proto deleted file mode 100644 index d8c2652..0000000 --- a/src/modules/ad/adapters/primaries/ad.proto +++ /dev/null @@ -1,83 +0,0 @@ -syntax = "proto3"; - -package ad; - -service AdsService { - rpc FindOneByUuid(AdByUuid) returns (Ad); - rpc FindAll(AdFilter) returns (Ads); - rpc Create(Ad) returns (AdByUuid); - rpc Update(Ad) returns (Ad); - rpc Delete(AdByUuid) returns (Empty); -} - -message AdByUuid { - string uuid = 1; -} - -message Ad { - string uuid = 1; - string userUuid = 2; - bool driver = 3; - bool passenger = 4; - Frequency frequency = 5; - optional string departureDateTime = 6; - string fromDate = 7; - string toDate = 8; - Schedule schedule = 9; - MarginDurations marginDurations = 10; - int32 seatsPassenger = 11; - int32 seatsDriver = 12; - bool strict = 13; - repeated Address addresses = 14; -} - -message Schedule { - optional string mon = 1; - optional string tue = 2; - optional string wed = 3; - optional string thu = 4; - optional string fri = 5; - optional string sat = 6; - optional string sun = 7; -} - -message MarginDurations { - int32 mon = 1; - int32 tue = 2; - int32 wed = 3; - int32 thu = 4; - int32 fri = 5; - int32 sat = 6; - int32 sun = 7; -} - -message Address { - string uuid = 1; - int32 position = 2; - float lon = 3; - float lat = 4; - optional string name = 5; - optional string houseNumber = 6; - optional string street = 7; - optional string locality = 8; - optional string postalCode = 9; - string country = 10; -} - - -enum Frequency { - PUNCTUAL = 1; - RECURRENT = 2; -} - -message AdFilter { - int32 page = 1; - int32 perPage = 2; -} - -message Ads { - repeated Ad data = 1; - int32 total = 2; -} - -message Empty {} diff --git a/src/modules/ad/adapters/secondaries/ads.repository.ts b/src/modules/ad/adapters/secondaries/ads.repository.ts deleted file mode 100644 index a4b0b28..0000000 --- a/src/modules/ad/adapters/secondaries/ads.repository.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { AdRepository } from '../../../database/domain/ad-repository'; -import { Ad } from '../../domain/entities/ad'; -//TODO : properly implement mutate operation to prisma -@Injectable() -export class AdsRepository extends AdRepository { - protected model = 'ad'; -} diff --git a/src/modules/ad/adapters/secondaries/default-params.provider.ts b/src/modules/ad/adapters/secondaries/default-params.provider.ts deleted file mode 100644 index 1dc0f09..0000000 --- a/src/modules/ad/adapters/secondaries/default-params.provider.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { DefaultParams } from '../../domain/types/default-params.type'; -import { IProvideParams } from '../../domain/interfaces/param-provider.interface'; - -@Injectable() -export class DefaultParamsProvider implements IProvideParams { - constructor(private readonly configService: ConfigService) {} - getParams = (): DefaultParams => ({ - MON_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')), - TUE_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')), - WED_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')), - THU_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')), - FRI_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')), - SAT_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')), - SUN_MARGIN: parseInt(this.configService.get('DEPARTURE_MARGIN')), - DRIVER: this.configService.get('ROLE') == 'driver', - SEATS_PROVIDED: parseInt(this.configService.get('SEATS_PROVIDED')), - PASSENGER: this.configService.get('ROLE') == 'passenger', - SEATS_REQUESTED: parseInt(this.configService.get('SEATS_REQUESTED')), - STRICT: this.configService.get('STRICT_FREQUENCY') == 'true', - }); -} diff --git a/src/modules/ad/commands/create-ad.command.ts b/src/modules/ad/commands/create-ad.command.ts deleted file mode 100644 index b4f1e8d..0000000 --- a/src/modules/ad/commands/create-ad.command.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { CreateAdRequest } from '../domain/dtos/create-ad.request'; - -export class CreateAdCommand { - readonly createAdRequest: CreateAdRequest; - - constructor(request: CreateAdRequest) { - this.createAdRequest = request; - } -} diff --git a/src/modules/ad/core/ad.entity.ts b/src/modules/ad/core/ad.entity.ts new file mode 100644 index 0000000..3d682ab --- /dev/null +++ b/src/modules/ad/core/ad.entity.ts @@ -0,0 +1,137 @@ +import { AggregateRoot, AggregateID } from '@libs/ddd'; +import { v4 } from 'uuid'; +import { AdCreatedDomainEvent } from './events/ad-created.domain-events'; +import { AdProps, CreateAdProps, DefaultAdProps } from './ad.types'; +import { Waypoint } from './value-objects/waypoint.value-object'; +import { MarginDurationsProps } from './value-objects/margin-durations.value-object'; + +export class AdEntity extends AggregateRoot { + protected readonly _id: AggregateID; + + static create = ( + create: CreateAdProps, + defaultAdProps: DefaultAdProps, + ): AdEntity => { + const id = v4(); + const props: AdProps = { ...create }; + const ad = new AdEntity({ id, props }) + .setMissingMarginDurations(defaultAdProps.marginDurations) + .setMissingStrict(defaultAdProps.strict) + .setDefaultDriverAndPassengerParameters({ + driver: defaultAdProps.driver, + passenger: defaultAdProps.passenger, + seatsProposed: defaultAdProps.seatsProposed, + seatsRequested: defaultAdProps.seatsRequested, + }) + .setMissingWaypointsPosition(); + ad.addEvent( + new AdCreatedDomainEvent({ + aggregateId: id, + userId: props.userId, + driver: props.driver, + passenger: props.passenger, + frequency: props.frequency, + fromDate: props.fromDate, + toDate: props.toDate, + monTime: props.schedule.mon, + tueTime: props.schedule.tue, + wedTime: props.schedule.wed, + thuTime: props.schedule.thu, + friTime: props.schedule.fri, + satTime: props.schedule.sat, + sunTime: props.schedule.sun, + monMarginDuration: props.marginDurations.mon, + tueMarginDuration: props.marginDurations.tue, + wedMarginDuration: props.marginDurations.wed, + thuMarginDuration: props.marginDurations.thu, + friMarginDuration: props.marginDurations.fri, + satMarginDuration: props.marginDurations.sat, + sunMarginDuration: props.marginDurations.sun, + seatsProposed: props.seatsProposed, + seatsRequested: props.seatsRequested, + strict: props.strict, + waypoints: props.waypoints.map((waypoint: Waypoint) => ({ + position: waypoint.position, + name: waypoint.address.name, + houseNumber: waypoint.address.houseNumber, + street: waypoint.address.street, + postalCode: waypoint.address.postalCode, + locality: waypoint.address.locality, + country: waypoint.address.postalCode, + lon: waypoint.address.coordinates.lon, + lat: waypoint.address.coordinates.lat, + })), + }), + ); + return ad; + }; + + private setMissingMarginDurations = ( + defaultMarginDurations: MarginDurationsProps, + ): AdEntity => { + if (!this.props.marginDurations) this.props.marginDurations = {}; + if (!this.props.marginDurations.mon) + this.props.marginDurations.mon = defaultMarginDurations.mon; + if (!this.props.marginDurations.tue) + this.props.marginDurations.tue = defaultMarginDurations.tue; + if (!this.props.marginDurations.wed) + this.props.marginDurations.wed = defaultMarginDurations.wed; + if (!this.props.marginDurations.thu) + this.props.marginDurations.thu = defaultMarginDurations.thu; + if (!this.props.marginDurations.fri) + this.props.marginDurations.fri = defaultMarginDurations.fri; + if (!this.props.marginDurations.sat) + this.props.marginDurations.sat = defaultMarginDurations.sat; + if (!this.props.marginDurations.sun) + this.props.marginDurations.sun = defaultMarginDurations.sun; + return this; + }; + + private setMissingStrict = (strict: boolean): AdEntity => { + if (this.props.strict === undefined) this.props.strict = strict; + return this; + }; + + private setDefaultDriverAndPassengerParameters = ( + defaultDriverAndPassengerParameters: DefaultDriverAndPassengerParameters, + ): AdEntity => { + this.props.driver = !!this.props.driver; + this.props.passenger = !!this.props.passenger; + if (!this.props.driver && !this.props.passenger) { + this.props.driver = defaultDriverAndPassengerParameters.driver; + this.props.seatsProposed = + defaultDriverAndPassengerParameters.seatsProposed; + this.props.passenger = defaultDriverAndPassengerParameters.passenger; + this.props.seatsRequested = + defaultDriverAndPassengerParameters.seatsRequested; + return this; + } + if (!this.props.seatsProposed || this.props.seatsProposed <= 0) + this.props.seatsProposed = + defaultDriverAndPassengerParameters.seatsProposed; + if (!this.props.seatsRequested || this.props.seatsRequested <= 0) + this.props.seatsRequested = + defaultDriverAndPassengerParameters.seatsRequested; + return this; + }; + + private setMissingWaypointsPosition = (): AdEntity => { + if (this.props.waypoints[0].position === undefined) { + for (let i = 0; i < this.props.waypoints.length; i++) { + this.props.waypoints[i].position = i; + } + } + return this; + }; + + validate(): void { + // entity business rules validation to protect it's invariant before saving entity to a database + } +} + +interface DefaultDriverAndPassengerParameters { + driver: boolean; + passenger: boolean; + seatsProposed: number; + seatsRequested: number; +} diff --git a/src/modules/ad/core/ad.errors.ts b/src/modules/ad/core/ad.errors.ts new file mode 100644 index 0000000..ec53993 --- /dev/null +++ b/src/modules/ad/core/ad.errors.ts @@ -0,0 +1,11 @@ +import { ExceptionBase } from '@libs/exceptions'; + +export class AdAlreadyExistsException extends ExceptionBase { + static readonly message = 'Ad already exists'; + + public readonly code = 'AD.ALREADY_EXISTS'; + + constructor(cause?: Error, metadata?: unknown) { + super(AdAlreadyExistsException.message, cause, metadata); + } +} diff --git a/src/modules/ad/core/ad.types.ts b/src/modules/ad/core/ad.types.ts new file mode 100644 index 0000000..b24502d --- /dev/null +++ b/src/modules/ad/core/ad.types.ts @@ -0,0 +1,49 @@ +import { MarginDurationsProps } from './value-objects/margin-durations.value-object'; +import { ScheduleProps } from './value-objects/schedule.value-object'; +import { WaypointProps } from './value-objects/waypoint.value-object'; + +// All properties that an Ad has +export interface AdProps { + userId: string; + driver: boolean; + passenger: boolean; + frequency: Frequency; + fromDate: string; + toDate: string; + schedule: ScheduleProps; + marginDurations: MarginDurationsProps; + seatsProposed: number; + seatsRequested: number; + strict: boolean; + waypoints: WaypointProps[]; +} + +// Properties that are needed for an Ad creation +export interface CreateAdProps { + userId: string; + driver: boolean; + passenger: boolean; + frequency: Frequency; + fromDate: string; + toDate: string; + schedule: ScheduleProps; + marginDurations: MarginDurationsProps; + seatsProposed: number; + seatsRequested: number; + strict: boolean; + waypoints: WaypointProps[]; +} + +export interface DefaultAdProps { + driver: boolean; + passenger: boolean; + marginDurations: MarginDurationsProps; + strict: boolean; + seatsProposed: number; + seatsRequested: number; +} + +export enum Frequency { + PUNCTUAL = 'PUNCTUAL', + RECURRENT = 'RECURRENT', +} diff --git a/src/modules/ad/core/commands/create-ad/create-ad.command.ts b/src/modules/ad/core/commands/create-ad/create-ad.command.ts new file mode 100644 index 0000000..f67d0c4 --- /dev/null +++ b/src/modules/ad/core/commands/create-ad/create-ad.command.ts @@ -0,0 +1,36 @@ +import { Command, CommandProps } from '@libs/ddd'; +import { Frequency } from '@modules/ad/core/ad.types'; +import { Schedule } from '../../types/schedule'; +import { MarginDurations } from '../../types/margin-durations'; +import { Waypoint } from '../../types/waypoint'; + +export class CreateAdCommand extends Command { + readonly userId: string; + readonly driver?: boolean; + readonly passenger?: boolean; + readonly frequency?: Frequency; + readonly fromDate: string; + readonly toDate: string; + readonly schedule: Schedule; + readonly marginDurations?: MarginDurations; + readonly seatsProposed?: number; + readonly seatsRequested?: number; + readonly strict?: boolean; + readonly waypoints: Waypoint[]; + + constructor(props: CommandProps) { + super(props); + this.userId = props.userId; + this.driver = props.driver; + this.passenger = props.passenger; + this.frequency = props.frequency; + this.fromDate = props.fromDate; + this.toDate = props.toDate; + this.schedule = props.schedule; + this.marginDurations = props.marginDurations; + this.seatsProposed = props.seatsProposed; + this.seatsRequested = props.seatsRequested; + this.strict = props.strict; + this.waypoints = props.waypoints; + } +} diff --git a/src/modules/ad/core/commands/create-ad/create-ad.service.ts b/src/modules/ad/core/commands/create-ad/create-ad.service.ts new file mode 100644 index 0000000..1fc16fc --- /dev/null +++ b/src/modules/ad/core/commands/create-ad/create-ad.service.ts @@ -0,0 +1,85 @@ +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { CreateAdCommand } from './create-ad.command'; +import { DefaultParams } from '@modules/ad/core/ports/default-params.type'; +import { Inject } from '@nestjs/common'; +import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens'; +import { AdRepositoryPort } from '@modules/ad/core/ports/ad.repository.port'; +import { DefaultParamsProviderPort } from '@modules/ad/core/ports/default-params-provider.port'; +import { AggregateID } from '@libs/ddd'; +import { AdAlreadyExistsException } from '@modules/ad/core/ad.errors'; +import { AdEntity } from '@modules/ad/core/ad.entity'; +import { ConflictException } from '@libs/exceptions'; +import { Waypoint } from '../../types/waypoint'; + +@CommandHandler(CreateAdCommand) +export class CreateAdService implements ICommandHandler { + private readonly _defaultParams: DefaultParams; + + constructor( + @Inject(AD_REPOSITORY) + private readonly repository: AdRepositoryPort, + @Inject(PARAMS_PROVIDER) + private readonly defaultParamsProvider: DefaultParamsProviderPort, + ) { + this._defaultParams = defaultParamsProvider.getParams(); + } + + async execute(command: CreateAdCommand): Promise { + const ad = AdEntity.create( + { + userId: command.userId, + driver: command.driver, + passenger: command.passenger, + frequency: command.frequency, + fromDate: command.fromDate, + toDate: command.toDate, + schedule: command.schedule, + marginDurations: command.marginDurations, + seatsProposed: command.seatsProposed, + seatsRequested: command.seatsRequested, + strict: command.strict, + waypoints: command.waypoints.map((waypoint: Waypoint) => ({ + position: waypoint.position, + address: { + name: waypoint.name, + houseNumber: waypoint.houseNumber, + street: waypoint.street, + postalCode: waypoint.postalCode, + locality: waypoint.locality, + country: waypoint.country, + coordinates: { + lon: waypoint.lon, + lat: waypoint.lat, + }, + }, + })), + }, + { + driver: this._defaultParams.DRIVER, + passenger: this._defaultParams.PASSENGER, + marginDurations: { + mon: this._defaultParams.MON_MARGIN, + tue: this._defaultParams.TUE_MARGIN, + wed: this._defaultParams.WED_MARGIN, + thu: this._defaultParams.THU_MARGIN, + fri: this._defaultParams.FRI_MARGIN, + sat: this._defaultParams.SAT_MARGIN, + sun: this._defaultParams.SUN_MARGIN, + }, + strict: this._defaultParams.STRICT, + seatsProposed: this._defaultParams.SEATS_PROPOSED, + seatsRequested: this._defaultParams.SEATS_REQUESTED, + }, + ); + + try { + await this.repository.insert(ad); + return ad.id; + } catch (error: any) { + if (error instanceof ConflictException) { + throw new AdAlreadyExistsException(error); + } + throw error; + } + } +} diff --git a/src/modules/ad/core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler.ts b/src/modules/ad/core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler.ts new file mode 100644 index 0000000..7913c7b --- /dev/null +++ b/src/modules/ad/core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler.ts @@ -0,0 +1,21 @@ +import { MessagePublisherPort } from '@libs/ports/message-publisher.port'; +import { Inject, Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; +import { AdCreatedDomainEvent } from '../events/ad-created.domain-events'; + +@Injectable() +export class PublishLogMessageWhenAdIsCreatedDomainEventHandler { + constructor( + @Inject(MESSAGE_PUBLISHER) + private readonly messagePublisher: MessagePublisherPort, + ) {} + + @OnEvent(AdCreatedDomainEvent.name, { async: true, promisify: true }) + async handle(event: AdCreatedDomainEvent): Promise { + this.messagePublisher.publish( + 'logging.ad.created.info', + JSON.stringify(event), + ); + } +} diff --git a/src/modules/ad/core/event-handlers/publish-message-when-ad-is-created.domain-event-handler.ts b/src/modules/ad/core/event-handlers/publish-message-when-ad-is-created.domain-event-handler.ts new file mode 100644 index 0000000..69e4d38 --- /dev/null +++ b/src/modules/ad/core/event-handlers/publish-message-when-ad-is-created.domain-event-handler.ts @@ -0,0 +1,18 @@ +import { MessagePublisherPort } from '@libs/ports/message-publisher.port'; +import { Inject, Injectable } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; +import { AdCreatedDomainEvent } from '../events/ad-created.domain-events'; + +@Injectable() +export class PublishMessageWhenAdIsCreatedDomainEventHandler { + constructor( + @Inject(MESSAGE_PUBLISHER) + private readonly messagePublisher: MessagePublisherPort, + ) {} + + @OnEvent(AdCreatedDomainEvent.name, { async: true, promisify: true }) + async handle(event: AdCreatedDomainEvent): Promise { + this.messagePublisher.publish('ad.created', JSON.stringify(event)); + } +} diff --git a/src/modules/ad/core/events/ad-created.domain-events.ts b/src/modules/ad/core/events/ad-created.domain-events.ts new file mode 100644 index 0000000..812e960 --- /dev/null +++ b/src/modules/ad/core/events/ad-created.domain-events.ts @@ -0,0 +1,68 @@ +import { DomainEvent, DomainEventProps } from '@libs/ddd'; + +export class AdCreatedDomainEvent extends DomainEvent { + readonly userId: string; + readonly driver: boolean; + readonly passenger: boolean; + readonly frequency: string; + readonly fromDate: string; + readonly toDate: string; + readonly monTime: string; + readonly tueTime: string; + readonly wedTime: string; + readonly thuTime: string; + readonly friTime: string; + readonly satTime: string; + readonly sunTime: string; + readonly monMarginDuration: number; + readonly tueMarginDuration: number; + readonly wedMarginDuration: number; + readonly thuMarginDuration: number; + readonly friMarginDuration: number; + readonly satMarginDuration: number; + readonly sunMarginDuration: number; + readonly seatsProposed: number; + readonly seatsRequested: number; + readonly strict: boolean; + readonly waypoints: Waypoint[]; + + constructor(props: DomainEventProps) { + super(props); + this.userId = props.userId; + this.driver = props.driver; + this.passenger = props.passenger; + this.frequency = props.frequency; + this.fromDate = props.fromDate; + this.toDate = props.toDate; + this.monTime = props.monTime; + this.tueTime = props.tueTime; + this.wedTime = props.wedTime; + this.thuTime = props.thuTime; + this.friTime = props.friTime; + this.satTime = props.satTime; + this.sunTime = props.sunTime; + this.monMarginDuration = props.monMarginDuration; + this.tueMarginDuration = props.tueMarginDuration; + this.wedMarginDuration = props.wedMarginDuration; + this.thuMarginDuration = props.thuMarginDuration; + this.friMarginDuration = props.friMarginDuration; + this.satMarginDuration = props.satMarginDuration; + this.sunMarginDuration = props.sunMarginDuration; + this.seatsProposed = props.seatsProposed; + this.seatsRequested = props.seatsRequested; + this.strict = props.strict; + this.waypoints = props.waypoints; + } +} + +export class Waypoint { + position: number; + name?: string; + houseNumber?: string; + street?: string; + locality?: string; + postalCode?: string; + country: string; + lon: number; + lat: number; +} diff --git a/src/modules/ad/core/ports/ad.repository.port.ts b/src/modules/ad/core/ports/ad.repository.port.ts new file mode 100644 index 0000000..93f8f77 --- /dev/null +++ b/src/modules/ad/core/ports/ad.repository.port.ts @@ -0,0 +1,4 @@ +import { RepositoryPort } from '@libs/ddd'; +import { AdEntity } from '../ad.entity'; + +export type AdRepositoryPort = RepositoryPort; diff --git a/src/modules/ad/core/ports/default-params-provider.port.ts b/src/modules/ad/core/ports/default-params-provider.port.ts new file mode 100644 index 0000000..e316b77 --- /dev/null +++ b/src/modules/ad/core/ports/default-params-provider.port.ts @@ -0,0 +1,5 @@ +import { DefaultParams } from './default-params.type'; + +export interface DefaultParamsProviderPort { + getParams(): DefaultParams; +} diff --git a/src/modules/ad/domain/types/default-params.type.ts b/src/modules/ad/core/ports/default-params.type.ts similarity index 83% rename from src/modules/ad/domain/types/default-params.type.ts rename to src/modules/ad/core/ports/default-params.type.ts index 8fee5e0..4368936 100644 --- a/src/modules/ad/domain/types/default-params.type.ts +++ b/src/modules/ad/core/ports/default-params.type.ts @@ -7,8 +7,9 @@ export type DefaultParams = { SAT_MARGIN: number; SUN_MARGIN: number; DRIVER: boolean; - SEATS_PROVIDED: number; + SEATS_PROPOSED: number; PASSENGER: boolean; SEATS_REQUESTED: number; STRICT: boolean; + DEFAULT_TIMEZONE: string; }; diff --git a/src/modules/ad/core/ports/time-converter.port.ts b/src/modules/ad/core/ports/time-converter.port.ts new file mode 100644 index 0000000..e48dbd0 --- /dev/null +++ b/src/modules/ad/core/ports/time-converter.port.ts @@ -0,0 +1,9 @@ +export interface TimeConverterPort { + localDateTimeToUtc( + date: string, + time: string, + timezone: string, + dst?: boolean, + ): Date; + utcDatetimeToLocalTime(isoString: string, timezone: string): string; +} diff --git a/src/modules/ad/core/ports/timezone-finder.port.ts b/src/modules/ad/core/ports/timezone-finder.port.ts new file mode 100644 index 0000000..72ba115 --- /dev/null +++ b/src/modules/ad/core/ports/timezone-finder.port.ts @@ -0,0 +1,3 @@ +export interface TimezoneFinderPort { + timezones(lon: number, lat: number, defaultTimezone?: string): string[]; +} diff --git a/src/modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query-handler.ts b/src/modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query-handler.ts new file mode 100644 index 0000000..547c572 --- /dev/null +++ b/src/modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query-handler.ts @@ -0,0 +1,17 @@ +import { IQueryHandler, QueryHandler } from '@nestjs/cqrs'; +import { FindAdByIdQuery } from './find-ad-by-id.query'; +import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens'; +import { AdRepositoryPort } from '../../ports/ad.repository.port'; +import { Inject } from '@nestjs/common'; +import { AdEntity } from '../../ad.entity'; + +@QueryHandler(FindAdByIdQuery) +export class FindAdByIdQueryHandler implements IQueryHandler { + constructor( + @Inject(AD_REPOSITORY) + private readonly repository: AdRepositoryPort, + ) {} + async execute(query: FindAdByIdQuery): Promise { + return await this.repository.findOneById(query.id, { waypoints: true }); + } +} diff --git a/src/modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query.ts b/src/modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query.ts new file mode 100644 index 0000000..defce96 --- /dev/null +++ b/src/modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query.ts @@ -0,0 +1,10 @@ +import { QueryBase } from '@libs/ddd/query.base'; + +export class FindAdByIdQuery extends QueryBase { + readonly id: string; + + constructor(id: string) { + super(); + this.id = id; + } +} diff --git a/src/modules/ad/core/types/address.ts b/src/modules/ad/core/types/address.ts new file mode 100644 index 0000000..bdf8e6b --- /dev/null +++ b/src/modules/ad/core/types/address.ts @@ -0,0 +1,10 @@ +import { Coordinates } from './coordinates'; + +export type Address = { + name?: string; + houseNumber?: string; + street?: string; + locality?: string; + postalCode?: string; + country: string; +} & Coordinates; diff --git a/src/modules/ad/core/types/coordinates.ts b/src/modules/ad/core/types/coordinates.ts new file mode 100644 index 0000000..8e149ed --- /dev/null +++ b/src/modules/ad/core/types/coordinates.ts @@ -0,0 +1,4 @@ +export type Coordinates = { + lon: number; + lat: number; +}; diff --git a/src/modules/ad/core/types/margin-durations.ts b/src/modules/ad/core/types/margin-durations.ts new file mode 100644 index 0000000..8e09329 --- /dev/null +++ b/src/modules/ad/core/types/margin-durations.ts @@ -0,0 +1,9 @@ +export type MarginDurations = { + mon?: number; + tue?: number; + wed?: number; + thu?: number; + fri?: number; + sat?: number; + sun?: number; +}; diff --git a/src/modules/ad/core/types/schedule.ts b/src/modules/ad/core/types/schedule.ts new file mode 100644 index 0000000..03f8485 --- /dev/null +++ b/src/modules/ad/core/types/schedule.ts @@ -0,0 +1,9 @@ +export type Schedule = { + mon?: string; + tue?: string; + wed?: string; + thu?: string; + fri?: string; + sat?: string; + sun?: string; +}; diff --git a/src/modules/ad/core/types/waypoint.ts b/src/modules/ad/core/types/waypoint.ts new file mode 100644 index 0000000..c73e024 --- /dev/null +++ b/src/modules/ad/core/types/waypoint.ts @@ -0,0 +1,5 @@ +import { Address } from './address'; + +export type Waypoint = { + position?: number; +} & Address; diff --git a/src/modules/ad/core/value-objects/address.value-object.ts b/src/modules/ad/core/value-objects/address.value-object.ts new file mode 100644 index 0000000..66a5da7 --- /dev/null +++ b/src/modules/ad/core/value-objects/address.value-object.ts @@ -0,0 +1,52 @@ +import { ValueObject } from '@libs/ddd'; +import { CoordinatesProps } from './coordinates.value-object'; + +/** Note: + * Value Objects with multiple properties can contain + * other Value Objects inside if needed. + * */ + +export interface AddressProps { + name?: string; + houseNumber?: string; + street?: string; + locality?: string; + postalCode?: string; + country: string; + coordinates: CoordinatesProps; +} + +export class Address extends ValueObject { + get name(): string { + return this.props.name; + } + + get houseNumber(): string { + return this.props.houseNumber; + } + + get street(): string { + return this.props.street; + } + + get locality(): string { + return this.props.locality; + } + + get postalCode(): string { + return this.props.postalCode; + } + + get country(): string { + return this.props.country; + } + + get coordinates(): CoordinatesProps { + return this.props.coordinates; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected validate(props: AddressProps): void { + return; + } +} diff --git a/src/modules/ad/core/value-objects/coordinates.value-object.ts b/src/modules/ad/core/value-objects/coordinates.value-object.ts new file mode 100644 index 0000000..7451d8a --- /dev/null +++ b/src/modules/ad/core/value-objects/coordinates.value-object.ts @@ -0,0 +1,26 @@ +import { ValueObject } from '@libs/ddd'; + +/** Note: + * Value Objects with multiple properties can contain + * other Value Objects inside if needed. + * */ + +export interface CoordinatesProps { + lon: number; + lat: number; +} + +export class Coordinates extends ValueObject { + get lon(): number { + return this.props.lon; + } + + get lat(): number { + return this.props.lat; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected validate(props: CoordinatesProps): void { + return; + } +} diff --git a/src/modules/ad/core/value-objects/margin-durations.value-object.ts b/src/modules/ad/core/value-objects/margin-durations.value-object.ts new file mode 100644 index 0000000..f69ccf8 --- /dev/null +++ b/src/modules/ad/core/value-objects/margin-durations.value-object.ts @@ -0,0 +1,79 @@ +import { ValueObject } from '@libs/ddd'; + +/** Note: + * Value Objects with multiple properties can contain + * other Value Objects inside if needed. + * */ + +export interface MarginDurationsProps { + mon?: number; + tue?: number; + wed?: number; + thu?: number; + fri?: number; + sat?: number; + sun?: number; +} + +export class MarginDurations extends ValueObject { + get mon(): number { + return this.props.mon; + } + + set mon(margin: number) { + this.props.mon = margin; + } + + get tue(): number { + return this.props.tue; + } + + set tue(margin: number) { + this.props.tue = margin; + } + + get wed(): number { + return this.props.wed; + } + + set wed(margin: number) { + this.props.wed = margin; + } + + get thu(): number { + return this.props.thu; + } + + set thu(margin: number) { + this.props.thu = margin; + } + + get fri(): number { + return this.props.fri; + } + + set fri(margin: number) { + this.props.fri = margin; + } + + get sat(): number { + return this.props.sat; + } + + set sat(margin: number) { + this.props.sat = margin; + } + + get sun(): number { + return this.props.sun; + } + + set sun(margin: number) { + this.props.sun = margin; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected validate(props: MarginDurationsProps): void { + return; + } +} diff --git a/src/modules/ad/core/value-objects/schedule.value-object.ts b/src/modules/ad/core/value-objects/schedule.value-object.ts new file mode 100644 index 0000000..da4696c --- /dev/null +++ b/src/modules/ad/core/value-objects/schedule.value-object.ts @@ -0,0 +1,51 @@ +import { ValueObject } from '@libs/ddd'; + +/** Note: + * Value Objects with multiple properties can contain + * other Value Objects inside if needed. + * */ + +export interface ScheduleProps { + mon?: string; + tue?: string; + wed?: string; + thu?: string; + fri?: string; + sat?: string; + sun?: string; +} + +export class Schedule extends ValueObject { + get mon(): string | undefined { + return this.props.mon; + } + + get tue(): string | undefined { + return this.props.tue; + } + + get wed(): string | undefined { + return this.props.wed; + } + + get thu(): string | undefined { + return this.props.thu; + } + + get fri(): string | undefined { + return this.props.fri; + } + + get sat(): string | undefined { + return this.props.sat; + } + + get sun(): string | undefined { + return this.props.sun; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected validate(props: ScheduleProps): void { + return; + } +} diff --git a/src/modules/ad/core/value-objects/waypoint.value-object.ts b/src/modules/ad/core/value-objects/waypoint.value-object.ts new file mode 100644 index 0000000..1b8395d --- /dev/null +++ b/src/modules/ad/core/value-objects/waypoint.value-object.ts @@ -0,0 +1,27 @@ +import { ValueObject } from '@libs/ddd'; +import { AddressProps } from './address.value-object'; + +/** Note: + * Value Objects with multiple properties can contain + * other Value Objects inside if needed. + * */ + +export interface WaypointProps { + position: number; + address: AddressProps; +} + +export class Waypoint extends ValueObject { + get position(): number { + return this.props.position; + } + + get address(): AddressProps { + return this.props.address; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected validate(props: WaypointProps): void { + return; + } +} diff --git a/src/modules/ad/domain/dtos/ad.creation.ts b/src/modules/ad/domain/dtos/ad.creation.ts deleted file mode 100644 index dd0bdeb..0000000 --- a/src/modules/ad/domain/dtos/ad.creation.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { AutoMap } from '@automapper/classes'; -import { Frequency } from '../types/frequency.enum'; -import { Address } from '../entities/address'; - -export class AdCreation { - @AutoMap() - uuid: string; - - @AutoMap() - userUuid: string; - - @AutoMap() - driver: boolean; - - @AutoMap() - passenger: boolean; - - @AutoMap() - frequency: Frequency; - - @AutoMap() - fromDate: Date; - - @AutoMap() - toDate: Date; - - @AutoMap() - monTime?: string; - - @AutoMap() - tueTime?: string; - - @AutoMap() - wedTime?: string; - - @AutoMap() - thuTime?: string; - - @AutoMap() - friTime?: string; - - @AutoMap() - satTime?: string; - - @AutoMap() - sunTime?: string; - - @AutoMap() - monMargin: number; - - @AutoMap() - tueMargin: number; - - @AutoMap() - wedMargin: number; - - @AutoMap() - thuMargin: number; - - @AutoMap() - friMargin: number; - - @AutoMap() - satMargin: number; - - @AutoMap() - sunMargin: number; - - @AutoMap() - seatsDriver: number; - - @AutoMap() - seatsPassenger: number; - - @AutoMap() - strict: boolean; - - @AutoMap() - createdAt: Date; - - @AutoMap() - updatedAt?: Date; - - @AutoMap() - addresses: { create: Address[] }; -} diff --git a/src/modules/ad/domain/dtos/address.dto.ts b/src/modules/ad/domain/dtos/address.dto.ts deleted file mode 100644 index 17a7b79..0000000 --- a/src/modules/ad/domain/dtos/address.dto.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { AutoMap } from '@automapper/classes'; -import { - IsInt, - IsLatitude, - IsLongitude, - IsOptional, - IsString, - IsUUID, -} from 'class-validator'; - -export class AddressDTO { - @IsOptional() - @IsUUID(4) - @AutoMap() - uuid?: string; - - @IsOptional() - @IsUUID(4) - @AutoMap() - adUuid?: string; - - @IsOptional() - @IsInt() - @AutoMap() - position?: number; - - @IsLongitude() - @AutoMap() - lon: number; - - @IsLatitude() - @AutoMap() - lat: number; - - @IsOptional() - @AutoMap() - name?: string; - - @IsOptional() - @IsString() - @AutoMap() - houseNumber?: string; - - @IsOptional() - @IsString() - @AutoMap() - street?: string; - - @IsOptional() - @IsString() - @AutoMap() - locality?: string; - - @IsOptional() - @IsString() - @AutoMap() - postalCode?: string; - - @IsString() - @AutoMap() - country: string; -} diff --git a/src/modules/ad/domain/dtos/create-ad.request.ts b/src/modules/ad/domain/dtos/create-ad.request.ts deleted file mode 100644 index cbb3f7b..0000000 --- a/src/modules/ad/domain/dtos/create-ad.request.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { AutoMap } from '@automapper/classes'; -import { - IsOptional, - IsBoolean, - IsDate, - IsInt, - IsEnum, - ValidateNested, - ArrayMinSize, - IsUUID, -} from 'class-validator'; -import { Frequency } from '../types/frequency.enum'; -import { Transform, Type } from 'class-transformer'; -import { intToFrequency } from './validators/frequency.mapping'; -import { MarginDTO } from './margin.dto'; -import { ScheduleDTO } from './schedule.dto'; -import { AddressDTO } from './address.dto'; -import { IsPunctualOrRecurrent } from './validators/decorators/is-punctual-or-recurrent.validator'; -import { HasProperDriverSeats } from './validators/decorators/has-driver-seats.validator'; -import { HasProperPassengerSeats } from './validators/decorators/has-passenger-seats.validator'; -import { HasProperPositionIndexes } from './validators/decorators/address-position.validator'; - -export class CreateAdRequest { - @IsOptional() - @IsUUID(4) - @AutoMap() - uuid?: string; - - @IsUUID(4) - @AutoMap() - userUuid: string; - - @IsOptional() - @IsBoolean() - @AutoMap() - driver?: boolean; - - @IsOptional() - @IsBoolean() - @AutoMap() - passenger?: boolean; - - @Transform(({ value }) => intToFrequency(value), { - toClassOnly: true, - }) - @IsEnum(Frequency) - @AutoMap() - frequency: Frequency; - - @IsOptional() - @IsPunctualOrRecurrent() - @Type(() => Date) - @IsDate() - @AutoMap() - departureDateTime?: Date; - - @IsOptional() - @IsPunctualOrRecurrent() - @Type(() => Date) - @IsDate() - @AutoMap() - fromDate?: Date; - - @IsOptional() - @IsPunctualOrRecurrent() - @Type(() => Date) - @IsDate() - @AutoMap() - toDate?: Date; - - @IsOptional() - @Type(() => ScheduleDTO) - @IsPunctualOrRecurrent() - @ValidateNested({ each: true }) - @AutoMap() - schedule: ScheduleDTO = {}; - - @IsOptional() - @Type(() => MarginDTO) - @ValidateNested({ each: true }) - @AutoMap() - marginDurations?: MarginDTO; - - @IsOptional() - @HasProperDriverSeats() - @IsInt() - @AutoMap() - seatsDriver?: number; - - @IsOptional() - @HasProperPassengerSeats() - @IsInt() - @AutoMap() - seatsPassenger?: number; - - @IsOptional() - @IsBoolean() - @AutoMap() - strict?: boolean; - - @ArrayMinSize(2) - @Type(() => AddressDTO) - @HasProperPositionIndexes() - @ValidateNested({ each: true }) - @AutoMap() - addresses: AddressDTO[]; -} diff --git a/src/modules/ad/domain/dtos/validators/address-position.ts b/src/modules/ad/domain/dtos/validators/address-position.ts deleted file mode 100644 index fe0cb0c..0000000 --- a/src/modules/ad/domain/dtos/validators/address-position.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { AddressDTO } from '../address.dto'; - -export const hasProperPositionIndexes = (value: AddressDTO[]): boolean => { - if (value.every((address) => address.position === undefined)) return true; - if (value.every((address) => typeof address.position === 'number')) { - value.sort((a, b) => a.position - b.position); - for (let i = 1; i < value.length; i++) { - if (value[i - 1].position >= value[i].position) return false; - } - return true; - } - return false; -}; diff --git a/src/modules/ad/domain/dtos/validators/decorators/address-position.validator.ts b/src/modules/ad/domain/dtos/validators/decorators/address-position.validator.ts deleted file mode 100644 index a21283e..0000000 --- a/src/modules/ad/domain/dtos/validators/decorators/address-position.validator.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ValidateBy, ValidationOptions, buildMessage } from 'class-validator'; -import { AddressDTO } from '../../address.dto'; -import { hasProperPositionIndexes } from '../address-position'; - -export const HasProperPositionIndexes = ( - validationOptions?: ValidationOptions, -): PropertyDecorator => - ValidateBy( - { - name: '', - constraints: [], - validator: { - validate: (value: AddressDTO[]): boolean => - hasProperPositionIndexes(value), - - defaultMessage: buildMessage( - () => - `indexes position incorrect, please provide a complete list of indexes or ordened list of adresses from start to end of journey`, - validationOptions, - ), - }, - }, - validationOptions, - ); diff --git a/src/modules/ad/domain/dtos/validators/decorators/has-driver-seats.validator.ts b/src/modules/ad/domain/dtos/validators/decorators/has-driver-seats.validator.ts deleted file mode 100644 index d1ba793..0000000 --- a/src/modules/ad/domain/dtos/validators/decorators/has-driver-seats.validator.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { - ValidateBy, - ValidationArguments, - ValidationOptions, - buildMessage, -} from 'class-validator'; -import { hasProperDriverSeats } from '../has-driver-seats'; - -export const HasProperDriverSeats = ( - validationOptions?: ValidationOptions, -): PropertyDecorator => - ValidateBy( - { - name: '', - constraints: [], - validator: { - validate: (value: any, args: ValidationArguments): boolean => - hasProperDriverSeats(args), - defaultMessage: buildMessage( - () => `driver and driver seats are not correct`, - validationOptions, - ), - }, - }, - validationOptions, - ); diff --git a/src/modules/ad/domain/dtos/validators/decorators/is-punctual-or-recurrent.validator.ts b/src/modules/ad/domain/dtos/validators/decorators/is-punctual-or-recurrent.validator.ts deleted file mode 100644 index f8ddf98..0000000 --- a/src/modules/ad/domain/dtos/validators/decorators/is-punctual-or-recurrent.validator.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - ValidateBy, - ValidationArguments, - ValidationOptions, - buildMessage, -} from 'class-validator'; -import { isPunctualOrRecurrent } from '../is-punctual-or-recurrent'; - -export const IsPunctualOrRecurrent = ( - validationOptions?: ValidationOptions, -): PropertyDecorator => - ValidateBy( - { - name: '', - constraints: [], - validator: { - validate: (value, args: ValidationArguments): boolean => - isPunctualOrRecurrent(args), - defaultMessage: buildMessage( - () => - `the departure Date and time , from date, to date and schedule must be properly set on recurrent or punctual ad`, - validationOptions, - ), - }, - }, - validationOptions, - ); diff --git a/src/modules/ad/domain/dtos/validators/frequency.mapping.ts b/src/modules/ad/domain/dtos/validators/frequency.mapping.ts deleted file mode 100644 index 030fa34..0000000 --- a/src/modules/ad/domain/dtos/validators/frequency.mapping.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Frequency } from '../../types/frequency.enum'; - -export const intToFrequency = (index: number): Frequency => { - if (index == 1) return Frequency.PUNCTUAL; - if (index == 2) return Frequency.RECURRENT; - return undefined; -}; diff --git a/src/modules/ad/domain/dtos/validators/has-driver-seats.ts b/src/modules/ad/domain/dtos/validators/has-driver-seats.ts deleted file mode 100644 index fb34229..0000000 --- a/src/modules/ad/domain/dtos/validators/has-driver-seats.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ValidationArguments } from 'class-validator'; - -export const hasProperDriverSeats = (args: ValidationArguments): boolean => { - if ( - args.object['driver'] === true && - typeof args.object['seatsDriver'] === 'number' - ) - return args.object['seatsDriver'] > 0; - if ( - (args.object['driver'] === false || - args.object['driver'] === null || - args.object['driver'] === undefined) && - (args.object['seatsDriver'] === 0 || - args.object['seatsDriver'] === null || - args.object['seatsDriver'] === undefined) - ) - return true; - return false; -}; diff --git a/src/modules/ad/domain/dtos/validators/has-passenger-seats.ts b/src/modules/ad/domain/dtos/validators/has-passenger-seats.ts deleted file mode 100644 index f96eeb9..0000000 --- a/src/modules/ad/domain/dtos/validators/has-passenger-seats.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ValidationArguments } from 'class-validator'; - -export const hasProperPassengerSeats = (args: ValidationArguments): boolean => { - if ( - args.object['passenger'] === true && - typeof args.object['seatsPassenger'] === 'number' - ) - return args.object['seatsPassenger'] > 0; - if ( - (args.object['passenger'] === false || - args.object['passenger'] === null || - args.object['passenger'] === undefined) && - (args.object['seatsPassenger'] === 0 || - args.object['seatsPassenger'] === null || - args.object['seatsPassenger'] === undefined) - ) - return true; - return false; -}; diff --git a/src/modules/ad/domain/dtos/validators/is-punctual-or-recurrent.ts b/src/modules/ad/domain/dtos/validators/is-punctual-or-recurrent.ts deleted file mode 100644 index 7c974d6..0000000 --- a/src/modules/ad/domain/dtos/validators/is-punctual-or-recurrent.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ValidationArguments } from 'class-validator'; -import { Frequency } from '../../types/frequency.enum'; - -const isPunctual = (args: ValidationArguments): boolean => - args.object['frequency'] === Frequency.PUNCTUAL && - args.object['departureDateTime'] instanceof Date && - !Object.keys(args.object['schedule']).length; - -const isRecurrent = (args: ValidationArguments): boolean => - args.object['frequency'] === Frequency.RECURRENT && - args.object['fromDate'] instanceof Date && - args.object['toDate'] instanceof Date && - Object.keys(args.object['schedule']).length > 0; - -export const isPunctualOrRecurrent = (args: ValidationArguments): boolean => - isPunctual(args) || isRecurrent(args); diff --git a/src/modules/ad/domain/entities/ad.ts b/src/modules/ad/domain/entities/ad.ts deleted file mode 100644 index d46e204..0000000 --- a/src/modules/ad/domain/entities/ad.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { AutoMap } from '@automapper/classes'; -import { - IsOptional, - IsString, - IsBoolean, - IsDate, - IsInt, - IsEnum, - ValidateNested, - ArrayMinSize, - IsUUID, -} from 'class-validator'; -import { Address } from '../entities/address'; -import { Frequency } from '../types/frequency.enum'; - -export class Ad { - @IsUUID(4) - @AutoMap() - uuid: string; - - @IsUUID(4) - @AutoMap() - userUuid: string; - - @IsBoolean() - @AutoMap() - driver: boolean; - - @IsBoolean() - @AutoMap() - passenger: boolean; - - @IsEnum(Frequency) - @AutoMap() - frequency: Frequency; - - @IsDate() - @AutoMap() - fromDate: Date; - - @IsDate() - @AutoMap() - toDate: Date; - - @IsOptional() - @IsDate() - @AutoMap() - monTime?: string; - - @IsOptional() - @IsString() - @AutoMap() - tueTime?: string; - - @IsOptional() - @IsString() - @AutoMap() - wedTime?: string; - - @IsOptional() - @IsString() - @AutoMap() - thuTime?: string; - - @IsOptional() - @IsString() - @AutoMap() - friTime?: string; - - @IsOptional() - @IsString() - @AutoMap() - satTime?: string; - - @IsOptional() - @IsString() - @AutoMap() - sunTime?: string; - - @IsInt() - @AutoMap() - monMargin: number; - - @IsInt() - @AutoMap() - tueMargin: number; - - @IsInt() - @AutoMap() - wedMargin: number; - - @IsInt() - @AutoMap() - thuMargin: number; - - @IsInt() - @AutoMap() - friMargin: number; - - @IsInt() - @AutoMap() - satMargin: number; - - @IsInt() - @AutoMap() - sunMargin: number; - - @IsInt() - @AutoMap() - seatsDriver: number; - - @IsInt() - @AutoMap() - seatsPassenger: number; - - @IsBoolean() - @AutoMap() - strict: boolean; - - @IsDate() - @AutoMap() - createdAt: Date; - - @IsDate() - @AutoMap() - updatedAt?: Date; - - @ArrayMinSize(2) - @ValidateNested({ each: true }) - @AutoMap(() => [Address]) - addresses: Address[]; -} diff --git a/src/modules/ad/domain/entities/address.ts b/src/modules/ad/domain/entities/address.ts deleted file mode 100644 index 756688f..0000000 --- a/src/modules/ad/domain/entities/address.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { AutoMap } from '@automapper/classes'; -import { IsInt, IsUUID } from 'class-validator'; - -export class Address { - @IsUUID(4) - @AutoMap() - uuid: string; - - @IsUUID(4) - @AutoMap() - adUuid: string; - - @IsInt() - @AutoMap() - position: number; - - @AutoMap() - lon: number; - - @AutoMap() - lat: number; - - @AutoMap() - name?: string; - - @AutoMap() - houseNumber?: string; - - @AutoMap() - street?: string; - - @AutoMap() - locality: string; - - @AutoMap() - postalCode: string; - - @AutoMap() - country: string; -} diff --git a/src/modules/ad/domain/entities/frequency.normaliser.ts b/src/modules/ad/domain/entities/frequency.normaliser.ts deleted file mode 100644 index 4d8c72a..0000000 --- a/src/modules/ad/domain/entities/frequency.normaliser.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { CreateAdRequest } from '../dtos/create-ad.request'; -import { Day } from '../types/day.enum'; - -import { Frequency } from '../types/frequency.enum'; - -export class FrequencyNormaliser { - fromDateResolver(createAdRequest: CreateAdRequest): Date { - if (createAdRequest.frequency === Frequency.PUNCTUAL) - return createAdRequest.departureDateTime; - return createAdRequest.fromDate; - } - toDateResolver(createAdRequest: CreateAdRequest): Date { - if (createAdRequest.frequency === Frequency.PUNCTUAL) - return createAdRequest.departureDateTime; - return createAdRequest.toDate; - } - scheduleResolver = ( - createAdRequest: CreateAdRequest, - day: number, - ): string => { - if ( - Object.keys(createAdRequest.schedule).length === 0 && - createAdRequest.frequency == Frequency.PUNCTUAL && - createAdRequest.departureDateTime.getDay() === day - ) - return `${createAdRequest.departureDateTime - .getHours() - .toString() - .padStart(2, '0')}:${createAdRequest.departureDateTime - .getMinutes() - .toString() - .padStart(2, '0')}`; - return createAdRequest.schedule[Day[day]]; - }; -} diff --git a/src/modules/ad/domain/interfaces/param-provider.interface.ts b/src/modules/ad/domain/interfaces/param-provider.interface.ts deleted file mode 100644 index 4169e3b..0000000 --- a/src/modules/ad/domain/interfaces/param-provider.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { DefaultParams } from '../types/default-params.type'; -export interface IProvideParams { - getParams(): DefaultParams; -} diff --git a/src/modules/ad/domain/types/day.enum.ts b/src/modules/ad/domain/types/day.enum.ts deleted file mode 100644 index 5a7f7f3..0000000 --- a/src/modules/ad/domain/types/day.enum.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum Day { - sun = 0, - mon, - tue, - wed, - thu, - fri, - sat, -} diff --git a/src/modules/ad/domain/types/frequency.enum.ts b/src/modules/ad/domain/types/frequency.enum.ts deleted file mode 100644 index 0126874..0000000 --- a/src/modules/ad/domain/types/frequency.enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum Frequency { - PUNCTUAL = 'PUNCTUAL', - RECURRENT = 'RECURRENT', -} diff --git a/src/modules/ad/domain/usecases/create-ad.usecase.ts b/src/modules/ad/domain/usecases/create-ad.usecase.ts deleted file mode 100644 index ac62877..0000000 --- a/src/modules/ad/domain/usecases/create-ad.usecase.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { Mapper } from '@automapper/core'; -import { InjectMapper } from '@automapper/nestjs'; -import { Inject } from '@nestjs/common'; -import { CommandHandler } from '@nestjs/cqrs'; -import { AdsRepository } from '../../adapters/secondaries/ads.repository'; -import { CreateAdCommand } from '../../commands/create-ad.command'; -import { CreateAdRequest } from '../dtos/create-ad.request'; -import { IProvideParams } from '../interfaces/param-provider.interface'; -import { DefaultParams } from '../types/default-params.type'; -import { AdCreation } from '../dtos/ad.creation'; -import { Ad } from '../entities/ad'; -import { PARAMS_PROVIDER } from '../../ad.constants'; -import { IPublishMessage } from '../../../../interfaces/message-publisher'; -import { MESSAGE_PUBLISHER } from '../../../../app.constants'; - -@CommandHandler(CreateAdCommand) -export class CreateAdUseCase { - private readonly defaultParams: DefaultParams; - private ad: AdCreation; - constructor( - private readonly repository: AdsRepository, - @Inject(MESSAGE_PUBLISHER) - private readonly messagePublisher: IPublishMessage, - @InjectMapper() private readonly mapper: Mapper, - @Inject(PARAMS_PROVIDER) - private readonly defaultParamsProvider: IProvideParams, - ) { - this.defaultParams = defaultParamsProvider.getParams(); - } - - async execute(command: CreateAdCommand): Promise { - this.ad = this.mapper.map( - command.createAdRequest, - CreateAdRequest, - AdCreation, - ); - this.setDefaultMarginDurations(); - this.setDefaultAddressesPosition(); - this.setDefaultDriverAndPassengerParameters(); - this.setDefaultStrict(); - - try { - const adCreated: Ad = await this.repository.create(this.ad); - this.messagePublisher.publish('ad.create', JSON.stringify(adCreated)); - this.messagePublisher.publish( - 'logging.ad.create.info', - JSON.stringify(adCreated), - ); - return adCreated; - } catch (error) { - let key = 'logging.ad.create.crit'; - if (error.message.includes('Already exists')) { - key = 'logging.ad.create.warning'; - } - this.messagePublisher.publish( - key, - JSON.stringify({ - command, - error, - }), - ); - throw error; - } - } - - private setDefaultMarginDurations = (): void => { - if (this.ad.monMargin === undefined) - this.ad.monMargin = this.defaultParams.MON_MARGIN; - if (this.ad.tueMargin === undefined) - this.ad.tueMargin = this.defaultParams.TUE_MARGIN; - if (this.ad.wedMargin === undefined) - this.ad.wedMargin = this.defaultParams.WED_MARGIN; - if (this.ad.thuMargin === undefined) - this.ad.thuMargin = this.defaultParams.THU_MARGIN; - if (this.ad.friMargin === undefined) - this.ad.friMargin = this.defaultParams.FRI_MARGIN; - if (this.ad.satMargin === undefined) - this.ad.satMargin = this.defaultParams.SAT_MARGIN; - if (this.ad.sunMargin === undefined) - this.ad.sunMargin = this.defaultParams.SUN_MARGIN; - }; - - private setDefaultStrict = (): void => { - if (this.ad.strict === undefined) - this.ad.strict = this.defaultParams.STRICT; - }; - - private setDefaultDriverAndPassengerParameters = (): void => { - this.ad.driver = !!this.ad.driver; - this.ad.passenger = !!this.ad.passenger; - if (!this.ad.driver && !this.ad.passenger) { - this.ad.driver = this.defaultParams.DRIVER; - this.ad.seatsDriver = this.defaultParams.SEATS_PROVIDED; - this.ad.passenger = this.defaultParams.PASSENGER; - this.ad.seatsPassenger = this.defaultParams.SEATS_REQUESTED; - return; - } - if (!this.ad.seatsDriver || this.ad.seatsDriver <= 0) - this.ad.seatsDriver = this.defaultParams.SEATS_PROVIDED; - if (!this.ad.seatsPassenger || this.ad.seatsPassenger <= 0) - this.ad.seatsPassenger = this.defaultParams.SEATS_REQUESTED; - }; - - private setDefaultAddressesPosition = (): void => { - if (this.ad.addresses.create[0].position === undefined) { - for (let i = 0; i < this.ad.addresses.create.length; i++) { - this.ad.addresses.create[i].position = i; - } - } - }; -} diff --git a/src/modules/ad/domain/usecases/find-ad-by-uuid.usecase.ts b/src/modules/ad/domain/usecases/find-ad-by-uuid.usecase.ts deleted file mode 100644 index 273b619..0000000 --- a/src/modules/ad/domain/usecases/find-ad-by-uuid.usecase.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Inject, NotFoundException } from '@nestjs/common'; -import { QueryHandler } from '@nestjs/cqrs'; -import { FindAdByUuidQuery } from '../../queries/find-ad-by-uuid.query'; -import { AdsRepository } from '../../adapters/secondaries/ads.repository'; -import { Ad } from '../entities/ad'; -import { MESSAGE_PUBLISHER } from '../../../../app.constants'; -import { IPublishMessage } from '../../../../interfaces/message-publisher'; - -@QueryHandler(FindAdByUuidQuery) -export class FindAdByUuidUseCase { - constructor( - private readonly repository: AdsRepository, - @Inject(MESSAGE_PUBLISHER) - private readonly messagePublisher: IPublishMessage, - ) {} - - async execute(findAdByUuid: FindAdByUuidQuery): Promise { - try { - const ad = await this.repository.findOneByUuid(findAdByUuid.uuid); - if (!ad) throw new NotFoundException(); - return ad; - } catch (error) { - this.messagePublisher.publish( - 'logging.ad.read.warning', - JSON.stringify({ - query: findAdByUuid, - error, - }), - ); - throw error; - } - } -} diff --git a/src/modules/ad/infrastructure/ad.repository.ts b/src/modules/ad/infrastructure/ad.repository.ts new file mode 100644 index 0000000..29a4dca --- /dev/null +++ b/src/modules/ad/infrastructure/ad.repository.ts @@ -0,0 +1,84 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { EventEmitter2 } from '@nestjs/event-emitter'; +import { AdEntity } from '../core/ad.entity'; +import { AdRepositoryPort } from '../core/ports/ad.repository.port'; +import { PrismaService } from '@libs/db/prisma.service'; +import { AdMapper } from '../ad.mapper'; +import { PrismaRepositoryBase } from '@libs/db/prisma-repository.base'; + +export type AdBaseModel = { + uuid: string; + userUuid: string; + driver: boolean; + passenger: boolean; + frequency: string; + fromDate: Date; + toDate: Date; + monTime: Date; + tueTime: Date; + wedTime: Date; + thuTime: Date; + friTime: Date; + satTime: Date; + sunTime: Date; + monMargin: number; + tueMargin: number; + wedMargin: number; + thuMargin: number; + friMargin: number; + satMargin: number; + sunMargin: number; + seatsProposed: number; + seatsRequested: number; + strict: boolean; + createdAt: Date; + updatedAt: Date; +}; + +export type AdReadModel = AdBaseModel & { + waypoints: WaypointModel[]; +}; + +export type AdWriteModel = AdBaseModel & { + waypoints: { + create: WaypointModel[]; + }; +}; + +export type WaypointModel = { + uuid: string; + position: number; + lon: number; + lat: number; + name?: string; + houseNumber?: string; + street?: string; + locality?: string; + postalCode?: string; + country: string; + createdAt: Date; + updatedAt: Date; +}; + +/** + * Repository is used for retrieving/saving domain entities + * */ +@Injectable() +export class AdRepository + extends PrismaRepositoryBase + implements AdRepositoryPort +{ + constructor( + prisma: PrismaService, + mapper: AdMapper, + eventEmitter: EventEmitter2, + ) { + super( + prisma.ad, + prisma, + mapper, + eventEmitter, + new Logger(AdRepository.name), + ); + } +} diff --git a/src/modules/ad/infrastructure/default-params-provider.ts b/src/modules/ad/infrastructure/default-params-provider.ts new file mode 100644 index 0000000..c27f5c1 --- /dev/null +++ b/src/modules/ad/infrastructure/default-params-provider.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { DefaultParamsProviderPort } from '../core/ports/default-params-provider.port'; +import { DefaultParams } from '../core/ports/default-params.type'; + +@Injectable() +export class DefaultParamsProvider implements DefaultParamsProviderPort { + constructor(private readonly _configService: ConfigService) {} + getParams = (): DefaultParams => ({ + MON_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')), + TUE_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')), + WED_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')), + THU_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')), + FRI_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')), + SAT_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')), + SUN_MARGIN: parseInt(this._configService.get('DEPARTURE_MARGIN')), + DRIVER: this._configService.get('ROLE') == 'driver', + SEATS_PROPOSED: parseInt(this._configService.get('SEATS_PROPOSED')), + PASSENGER: this._configService.get('ROLE') == 'passenger', + SEATS_REQUESTED: parseInt(this._configService.get('SEATS_REQUESTED')), + STRICT: this._configService.get('STRICT_FREQUENCY') == 'true', + DEFAULT_TIMEZONE: this._configService.get('DEFAULT_TIMEZONE'), + }); +} diff --git a/src/modules/health/adapters/secondaries/message-publisher.ts b/src/modules/ad/infrastructure/message-publisher.ts similarity index 66% rename from src/modules/health/adapters/secondaries/message-publisher.ts rename to src/modules/ad/infrastructure/message-publisher.ts index 98a963b..69122e5 100644 --- a/src/modules/health/adapters/secondaries/message-publisher.ts +++ b/src/modules/ad/infrastructure/message-publisher.ts @@ -1,10 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; -import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; -import { IPublishMessage } from 'src/interfaces/message-publisher'; +import { MESSAGE_BROKER_PUBLISHER } from '@src/app.constants'; +import { MessagePublisherPort } from '@libs/ports/message-publisher.port'; @Injectable() -export class MessagePublisher implements IPublishMessage { +export class MessagePublisher implements MessagePublisherPort { constructor( @Inject(MESSAGE_BROKER_PUBLISHER) private readonly messageBrokerPublisher: MessageBrokerPublisher, diff --git a/src/modules/ad/infrastructure/time-converter.ts b/src/modules/ad/infrastructure/time-converter.ts new file mode 100644 index 0000000..b94b14a --- /dev/null +++ b/src/modules/ad/infrastructure/time-converter.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { TimeConverterPort } from '../core/ports/time-converter.port'; +import { DateTime, TimeZone } from 'timezonecomplete'; + +@Injectable() +export class TimeConverter implements TimeConverterPort { + localDateTimeToUtc = ( + date: string, + time: string, + timezone: string, + dst?: boolean, + ): Date => { + try { + if (!date || !time || !timezone) throw new Error(); + return new Date( + new DateTime(`${date}T${time}`, TimeZone.zone(timezone, dst)) + .convert(TimeZone.zone('UTC')) + .toIsoString(), + ); + } catch (e) { + return undefined; + } + }; + + utcDatetimeToLocalTime = (isoString: string, timezone: string): string => { + try { + return new DateTime(isoString) + .convert(TimeZone.zone(timezone)) + .toString() + .split('T')[1] + .substring(0, 5); + } catch (e) { + return undefined; + } + }; +} diff --git a/src/modules/ad/infrastructure/timezone-finder.ts b/src/modules/ad/infrastructure/timezone-finder.ts new file mode 100644 index 0000000..be990b3 --- /dev/null +++ b/src/modules/ad/infrastructure/timezone-finder.ts @@ -0,0 +1,16 @@ +import { Injectable } from '@nestjs/common'; +import { TimezoneFinderPort } from '../core/ports/timezone-finder.port'; +import { find } from 'geo-tz'; + +@Injectable() +export class TimezoneFinder implements TimezoneFinderPort { + timezones = ( + lon: number, + lat: number, + defaultTimezone?: string, + ): string[] => { + const foundTimezones = find(lat, lon); + if (defaultTimezone && foundTimezones.length == 0) return [defaultTimezone]; + return foundTimezones; + }; +} diff --git a/src/modules/ad/interface/dtos/ad.paginated.response.dto.ts b/src/modules/ad/interface/dtos/ad.paginated.response.dto.ts new file mode 100644 index 0000000..e680096 --- /dev/null +++ b/src/modules/ad/interface/dtos/ad.paginated.response.dto.ts @@ -0,0 +1,6 @@ +import { PaginatedResponseDto } from '@libs/api/paginated.response.base'; +import { AdResponseDto } from './ad.response.dto'; + +export class AdPaginatedResponseDto extends PaginatedResponseDto { + readonly data: readonly AdResponseDto[]; +} diff --git a/src/modules/ad/interface/dtos/ad.response.dto.ts b/src/modules/ad/interface/dtos/ad.response.dto.ts new file mode 100644 index 0000000..61ec321 --- /dev/null +++ b/src/modules/ad/interface/dtos/ad.response.dto.ts @@ -0,0 +1,43 @@ +import { ResponseBase } from '@libs/api/response.base'; +import { Frequency } from '@modules/ad/core/ad.types'; + +export class AdResponseDto extends ResponseBase { + userId: string; + driver: boolean; + passenger: boolean; + frequency: Frequency; + fromDate: string; + toDate: string; + schedule: { + mon?: string; + tue?: string; + wed?: string; + thu?: string; + fri?: string; + sat?: string; + sun?: string; + }; + marginDurations: { + mon?: number; + tue?: number; + wed?: number; + thu?: number; + fri?: number; + sat?: number; + sun?: number; + }; + seatsProposed: number; + seatsRequested: number; + strict: boolean; + waypoints: { + position: number; + name?: string; + houseNumber?: string; + street?: string; + postalCode?: string; + locality?: string; + country: string; + lon: number; + lat: number; + }[]; +} diff --git a/src/modules/ad/interface/grpc-controllers/ad.proto b/src/modules/ad/interface/grpc-controllers/ad.proto new file mode 100644 index 0000000..dd7cbe0 --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/ad.proto @@ -0,0 +1,80 @@ +syntax = "proto3"; + +package ad; + +service AdsService { + rpc FindOneById(AdById) returns (Ad); + rpc FindAll(AdFilter) returns (Ads); + rpc Create(Ad) returns (AdById); + rpc Update(Ad) returns (Ad); + rpc Delete(AdById) returns (Empty); +} + +message AdById { + string id = 1; +} + +message Ad { + string id = 1; + string userId = 2; + bool driver = 3; + bool passenger = 4; + Frequency frequency = 5; + string fromDate = 6; + string toDate = 7; + Schedule schedule = 8; + MarginDurations marginDurations = 9; + int32 seatsProposed = 10; + int32 seatsRequested = 11; + bool strict = 12; + repeated Waypoint waypoints = 13; +} + +message Schedule { + string mon = 1; + string tue = 2; + string wed = 3; + string thu = 4; + string fri = 5; + string sat = 6; + string sun = 7; +} + +message MarginDurations { + int32 mon = 1; + int32 tue = 2; + int32 wed = 3; + int32 thu = 4; + int32 fri = 5; + int32 sat = 6; + int32 sun = 7; +} + +message Waypoint { + int32 position = 1; + float lon = 2; + float lat = 3; + string name = 4; + string houseNumber = 5; + string street = 6; + string locality = 7; + string postalCode = 8; + string country = 9; +} + +enum Frequency { + PUNCTUAL = 1; + RECURRENT = 2; +} + +message AdFilter { + int32 page = 1; + int32 perPage = 2; +} + +message Ads { + repeated Ad data = 1; + int32 total = 2; +} + +message Empty {} diff --git a/src/modules/ad/interface/grpc-controllers/create-ad.grpc.controller.ts b/src/modules/ad/interface/grpc-controllers/create-ad.grpc.controller.ts new file mode 100644 index 0000000..6a14291 --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/create-ad.grpc.controller.ts @@ -0,0 +1,41 @@ +import { Controller, UsePipes } from '@nestjs/common'; +import { CommandBus } from '@nestjs/cqrs'; +import { GrpcMethod, RpcException } from '@nestjs/microservices'; +import { CreateAdRequestDto } from './dtos/create-ad.request.dto'; +import { CreateAdCommand } from '../../core/commands/create-ad/create-ad.command'; +import { AggregateID } from '@libs/ddd'; +import { AdAlreadyExistsException } from '../../core/ad.errors'; +import { IdResponse } from '@libs/api/id.response.dto'; +import { RpcExceptionCode } from '@libs/exceptions/rpc-exception.codes.enum'; +import { RpcValidationPipe } from '@libs/utils/pipes/rpc.validation-pipe'; + +@UsePipes( + new RpcValidationPipe({ + whitelist: false, + forbidUnknownValues: false, + }), +) +@Controller() +export class CreateAdGrpcController { + constructor(private readonly commandBus: CommandBus) {} + + @GrpcMethod('AdsService', 'Create') + async create(data: CreateAdRequestDto): Promise { + try { + const aggregateID: AggregateID = await this.commandBus.execute( + new CreateAdCommand(data), + ); + return new IdResponse(aggregateID); + } catch (error: any) { + if (error instanceof AdAlreadyExistsException) + throw new RpcException({ + code: RpcExceptionCode.ALREADY_EXISTS, + message: error.message, + }); + throw new RpcException({ + code: RpcExceptionCode.UNKNOWN, + message: error.message, + }); + } + } +} diff --git a/src/modules/ad/interface/grpc-controllers/dtos/address.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/address.dto.ts new file mode 100644 index 0000000..9659d96 --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/dtos/address.dto.ts @@ -0,0 +1,26 @@ +import { IsOptional, IsString } from 'class-validator'; +import { CoordinatesDto as CoordinatesDto } from './coordinates.dto'; + +export class AddressDto extends CoordinatesDto { + @IsOptional() + name?: string; + + @IsOptional() + @IsString() + houseNumber?: string; + + @IsOptional() + @IsString() + street?: string; + + @IsOptional() + @IsString() + locality?: string; + + @IsOptional() + @IsString() + postalCode?: string; + + @IsString() + country: string; +} diff --git a/src/modules/ad/interface/grpc-controllers/dtos/coordinates.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/coordinates.dto.ts new file mode 100644 index 0000000..3e622be --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/dtos/coordinates.dto.ts @@ -0,0 +1,17 @@ +import { Transform } from 'class-transformer'; +import { IsLatitude, IsLongitude } from 'class-validator'; +import { toPrecision } from './transformers/to-precision'; + +export class CoordinatesDto { + @Transform(({ value }) => toPrecision(value, 6), { + toClassOnly: true, + }) + @IsLongitude() + lon: number; + + @Transform(({ value }) => toPrecision(value, 6), { + toClassOnly: true, + }) + @IsLatitude() + lat: number; +} diff --git a/src/modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto.ts new file mode 100644 index 0000000..6d07d97 --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto.ts @@ -0,0 +1,79 @@ +import { + IsOptional, + IsBoolean, + IsInt, + IsEnum, + ValidateNested, + ArrayMinSize, + IsUUID, + IsArray, + IsISO8601, +} from 'class-validator'; +import { Transform, Type } from 'class-transformer'; +import { ScheduleDto } from './schedule.dto'; +import { MarginDurationsDto } from './margin-durations.dto'; +import { WaypointDto } from './waypoint.dto'; +import { intToFrequency } from './transformers/int-to-frequency'; +import { IsSchedule } from './validators/decorators/is-schedule.decorator'; +import { HasValidPositionIndexes } from './validators/decorators/has-valid-position-indexes.decorator'; +import { Frequency } from '@modules/ad/core/ad.types'; + +export class CreateAdRequestDto { + @IsUUID(4) + userId: string; + + @IsOptional() + @IsBoolean() + driver?: boolean; + + @IsOptional() + @IsBoolean() + passenger?: boolean; + + @Transform(({ value }) => intToFrequency(value), { + toClassOnly: true, + }) + @IsEnum(Frequency) + frequency: Frequency; + + @IsISO8601({ + strict: true, + strictSeparator: true, + }) + fromDate: string; + + @IsISO8601({ + strict: true, + strictSeparator: true, + }) + toDate: string; + + @Type(() => ScheduleDto) + @IsSchedule() + @ValidateNested({ each: true }) + schedule: ScheduleDto; + + @IsOptional() + @Type(() => MarginDurationsDto) + @ValidateNested({ each: true }) + marginDurations?: MarginDurationsDto; + + @IsOptional() + @IsInt() + seatsProposed?: number; + + @IsOptional() + @IsInt() + seatsRequested?: number; + + @IsOptional() + @IsBoolean() + strict?: boolean; + + @Type(() => WaypointDto) + @IsArray() + @ArrayMinSize(2) + @HasValidPositionIndexes() + @ValidateNested({ each: true }) + waypoints: WaypointDto[]; +} diff --git a/src/modules/ad/domain/dtos/find-ad-by-uuid.request.ts b/src/modules/ad/interface/grpc-controllers/dtos/find-ad-by-id.request.dto.ts similarity index 63% rename from src/modules/ad/domain/dtos/find-ad-by-uuid.request.ts rename to src/modules/ad/interface/grpc-controllers/dtos/find-ad-by-id.request.dto.ts index 9741962..ad485fc 100644 --- a/src/modules/ad/domain/dtos/find-ad-by-uuid.request.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/find-ad-by-id.request.dto.ts @@ -1,7 +1,7 @@ import { IsNotEmpty, IsString } from 'class-validator'; -export class FindAdByUuidRequest { +export class FindAdByIdRequestDto { @IsString() @IsNotEmpty() - uuid: string; + id: string; } diff --git a/src/modules/ad/domain/dtos/margin.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/margin-durations.dto.ts similarity index 69% rename from src/modules/ad/domain/dtos/margin.dto.ts rename to src/modules/ad/interface/grpc-controllers/dtos/margin-durations.dto.ts index a6d7e61..5637707 100644 --- a/src/modules/ad/domain/dtos/margin.dto.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/margin-durations.dto.ts @@ -1,39 +1,31 @@ -import { AutoMap } from '@automapper/classes'; import { IsInt, IsOptional } from 'class-validator'; -export class MarginDTO { +export class MarginDurationsDto { @IsOptional() @IsInt() - @AutoMap() mon?: number; @IsOptional() @IsInt() - @AutoMap() tue?: number; @IsOptional() @IsInt() - @AutoMap() wed?: number; @IsOptional() @IsInt() - @AutoMap() thu?: number; @IsOptional() @IsInt() - @AutoMap() fri?: number; @IsOptional() @IsInt() - @AutoMap() sat?: number; @IsOptional() @IsInt() - @AutoMap() sun?: number; } diff --git a/src/modules/ad/domain/dtos/schedule.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/schedule.dto.ts similarity index 72% rename from src/modules/ad/domain/dtos/schedule.dto.ts rename to src/modules/ad/interface/grpc-controllers/dtos/schedule.dto.ts index 3918c57..7316a64 100644 --- a/src/modules/ad/domain/dtos/schedule.dto.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/schedule.dto.ts @@ -1,39 +1,31 @@ -import { AutoMap } from '@automapper/classes'; import { IsOptional, IsMilitaryTime } from 'class-validator'; -export class ScheduleDTO { +export class ScheduleDto { @IsOptional() @IsMilitaryTime() - @AutoMap() mon?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() tue?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() wed?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() thu?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() fri?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() sat?: string; @IsOptional() @IsMilitaryTime() - @AutoMap() sun?: string; } diff --git a/src/modules/ad/interface/grpc-controllers/dtos/transformers/int-to-frequency.ts b/src/modules/ad/interface/grpc-controllers/dtos/transformers/int-to-frequency.ts new file mode 100644 index 0000000..4ec6a89 --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/dtos/transformers/int-to-frequency.ts @@ -0,0 +1,7 @@ +import { Frequency } from '@modules/ad/core/ad.types'; + +export const intToFrequency = (frequencyAsInt: number): Frequency => { + if (frequencyAsInt == 1) return Frequency.PUNCTUAL; + if (frequencyAsInt == 2) return Frequency.RECURRENT; + throw new Error('Unknown frequency value'); +}; diff --git a/src/modules/ad/interface/grpc-controllers/dtos/transformers/to-precision.ts b/src/modules/ad/interface/grpc-controllers/dtos/transformers/to-precision.ts new file mode 100644 index 0000000..997e89c --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/dtos/transformers/to-precision.ts @@ -0,0 +1,4 @@ +export const toPrecision = (input: number, precision: number): number => { + const multiplier = 10 ** precision; + return Math.round((input + Number.EPSILON) * multiplier) / multiplier; +}; diff --git a/src/modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-valid-position-indexes.decorator.ts b/src/modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-valid-position-indexes.decorator.ts new file mode 100644 index 0000000..87e3a36 --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-valid-position-indexes.decorator.ts @@ -0,0 +1,22 @@ +import { ValidateBy, ValidationOptions, buildMessage } from 'class-validator'; +import { hasValidPositionIndexes } from '../has-valid-position-indexes.validator'; +import { WaypointDto } from '../../waypoint.dto'; + +export const HasValidPositionIndexes = ( + validationOptions?: ValidationOptions, +): PropertyDecorator => + ValidateBy( + { + name: '', + constraints: [], + validator: { + validate: (waypoints: WaypointDto[]): boolean => + hasValidPositionIndexes(waypoints), + defaultMessage: buildMessage( + () => `invalid waypoints positions`, + validationOptions, + ), + }, + }, + validationOptions, + ); diff --git a/src/modules/ad/domain/dtos/validators/decorators/has-passenger-seats.validator.ts b/src/modules/ad/interface/grpc-controllers/dtos/validators/decorators/is-schedule.decorator.ts similarity index 67% rename from src/modules/ad/domain/dtos/validators/decorators/has-passenger-seats.validator.ts rename to src/modules/ad/interface/grpc-controllers/dtos/validators/decorators/is-schedule.decorator.ts index 26dc5de..5a80a19 100644 --- a/src/modules/ad/domain/dtos/validators/decorators/has-passenger-seats.validator.ts +++ b/src/modules/ad/interface/grpc-controllers/dtos/validators/decorators/is-schedule.decorator.ts @@ -4,9 +4,8 @@ import { ValidationOptions, buildMessage, } from 'class-validator'; -import { hasProperPassengerSeats } from '../has-passenger-seats'; -export const HasProperPassengerSeats = ( +export const IsSchedule = ( validationOptions?: ValidationOptions, ): PropertyDecorator => ValidateBy( @@ -14,10 +13,11 @@ export const HasProperPassengerSeats = ( name: '', constraints: [], validator: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars validate: (value, args: ValidationArguments): boolean => - hasProperPassengerSeats(args), + Object.keys(value).length > 0, defaultMessage: buildMessage( - () => `passenger and passenger seats are not correct`, + () => `schedule is invalid`, validationOptions, ), }, diff --git a/src/modules/ad/interface/grpc-controllers/dtos/validators/has-valid-position-indexes.validator.ts b/src/modules/ad/interface/grpc-controllers/dtos/validators/has-valid-position-indexes.validator.ts new file mode 100644 index 0000000..04504a8 --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/dtos/validators/has-valid-position-indexes.validator.ts @@ -0,0 +1,17 @@ +import { WaypointDto } from '../waypoint.dto'; + +export const hasValidPositionIndexes = (waypoints: WaypointDto[]): boolean => { + if (!waypoints) return false; + if (waypoints.length == 0) return false; + if (waypoints.every((waypoint) => waypoint.position === undefined)) + return false; + if (waypoints.every((waypoint) => typeof waypoint.position === 'number')) { + const positions = Array.from(waypoints, (waypoint) => waypoint.position); + positions.sort(); + for (let i = 1; i < positions.length; i++) + if (positions[i] != positions[i - 1] + 1) return false; + + return true; + } + return false; +}; diff --git a/src/modules/ad/interface/grpc-controllers/dtos/waypoint.dto.ts b/src/modules/ad/interface/grpc-controllers/dtos/waypoint.dto.ts new file mode 100644 index 0000000..ded5386 --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/dtos/waypoint.dto.ts @@ -0,0 +1,8 @@ +import { IsInt, IsOptional } from 'class-validator'; +import { AddressDto } from './address.dto'; + +export class WaypointDto extends AddressDto { + @IsOptional() + @IsInt() + position?: number; +} diff --git a/src/modules/ad/interface/grpc-controllers/find-ad-by-id.grpc.controller.ts b/src/modules/ad/interface/grpc-controllers/find-ad-by-id.grpc.controller.ts new file mode 100644 index 0000000..37f937a --- /dev/null +++ b/src/modules/ad/interface/grpc-controllers/find-ad-by-id.grpc.controller.ts @@ -0,0 +1,46 @@ +import { Controller, UsePipes } from '@nestjs/common'; +import { QueryBus } from '@nestjs/cqrs'; +import { GrpcMethod, RpcException } from '@nestjs/microservices'; +import { FindAdByIdRequestDto } from './dtos/find-ad-by-id.request.dto'; +import { FindAdByIdQuery } from '@modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query'; +import { AdResponseDto } from '../dtos/ad.response.dto'; +import { AdEntity } from '@modules/ad/core/ad.entity'; +import { AdMapper } from '@modules/ad/ad.mapper'; +import { NotFoundException } from '@libs/exceptions'; +import { RpcExceptionCode } from '@libs/exceptions/rpc-exception.codes.enum'; +import { RpcValidationPipe } from '@libs/utils/pipes/rpc.validation-pipe'; + +@UsePipes( + new RpcValidationPipe({ + whitelist: false, + forbidUnknownValues: false, + }), +) +@Controller() +export class FindAdByIdGrpcController { + constructor( + protected readonly mapper: AdMapper, + private readonly queryBus: QueryBus, + ) {} + + @GrpcMethod('AdsService', 'FindOneById') + async findOnebyId(data: FindAdByIdRequestDto): Promise { + try { + const ad: AdEntity = await this.queryBus.execute( + new FindAdByIdQuery(data.id), + ); + return this.mapper.toResponse(ad); + } catch (e) { + if (e instanceof NotFoundException) { + throw new RpcException({ + code: RpcExceptionCode.NOT_FOUND, + message: e.message, + }); + } + throw new RpcException({ + code: RpcExceptionCode.UNKNOWN, + message: e.message, + }); + } + } +} diff --git a/src/modules/ad/mappers/ad.profile.ts b/src/modules/ad/mappers/ad.profile.ts deleted file mode 100644 index 106b58a..0000000 --- a/src/modules/ad/mappers/ad.profile.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { createMap, forMember, mapFrom, Mapper } from '@automapper/core'; -import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; -import { Injectable } from '@nestjs/common'; -import { Ad } from '../domain/entities/ad'; -import { AdPresenter } from '../adapters/primaries/ad.presenter'; -import { CreateAdRequest } from '../domain/dtos/create-ad.request'; -import { AdCreation } from '../domain/dtos/ad.creation'; -import { FrequencyNormaliser } from '../domain/entities/frequency.normaliser'; -import { Day } from '../domain/types/day.enum'; - -@Injectable() -export class AdProfile extends AutomapperProfile { - frequencyNormaliser = new FrequencyNormaliser(); - constructor(@InjectMapper() mapper: Mapper) { - super(mapper); - } - override get profile() { - return (mapper) => { - createMap(mapper, Ad, AdPresenter); - createMap( - mapper, - CreateAdRequest, - AdCreation, - forMember( - (destination) => destination.monMargin, - mapFrom((source) => source.marginDurations?.mon), - ), - forMember( - (destination) => destination.tueMargin, - mapFrom((source) => source.marginDurations?.tue), - ), - forMember( - (destination) => destination.wedMargin, - mapFrom((source) => source.marginDurations?.wed), - ), - forMember( - (destination) => destination.thuMargin, - mapFrom((source) => source.marginDurations?.thu), - ), - forMember( - (destination) => destination.friMargin, - mapFrom((source) => source.marginDurations?.fri), - ), - forMember( - (destination) => destination.satMargin, - mapFrom((source) => source.marginDurations?.sat), - ), - forMember( - (destination) => destination.sunMargin, - mapFrom((source) => source.marginDurations?.sun), - ), - forMember( - (destination) => destination.monTime, - mapFrom((source) => source.schedule.mon), - ), - forMember( - (destination) => destination.tueTime, - mapFrom((source) => source.schedule.tue), - ), - forMember( - (destination) => destination.wedTime, - mapFrom((source) => source.schedule.wed), - ), - forMember( - (destination) => destination.thuTime, - mapFrom((source) => source.schedule.thu), - ), - forMember( - (destination) => destination.friTime, - mapFrom((source) => source.schedule.fri), - ), - forMember( - (destination) => destination.satTime, - mapFrom((source) => source.schedule.sat), - ), - forMember( - (destination) => destination.sunTime, - mapFrom((source) => source.schedule.sun), - ), - forMember( - (destination) => destination.addresses.create, - mapFrom((source) => source.addresses), - ), - forMember( - (destination) => destination.fromDate, - mapFrom((source) => - this.frequencyNormaliser.fromDateResolver(source), - ), - ), - forMember( - (destination) => destination.toDate, - mapFrom((source) => this.frequencyNormaliser.toDateResolver(source)), - ), - forMember( - (destination) => destination.monTime, - mapFrom((source) => - this.frequencyNormaliser.scheduleResolver(source, Day.mon), - ), - ), - forMember( - (destination) => destination.tueTime, - mapFrom((source) => - this.frequencyNormaliser.scheduleResolver(source, Day.tue), - ), - ), - forMember( - (destination) => destination.wedTime, - mapFrom((source) => - this.frequencyNormaliser.scheduleResolver(source, Day.wed), - ), - ), - forMember( - (destination) => destination.thuTime, - mapFrom((source) => - this.frequencyNormaliser.scheduleResolver(source, Day.thu), - ), - ), - forMember( - (destination) => destination.friTime, - mapFrom((source) => - this.frequencyNormaliser.scheduleResolver(source, Day.fri), - ), - ), - forMember( - (destination) => destination.satTime, - mapFrom((source) => - this.frequencyNormaliser.scheduleResolver(source, Day.sat), - ), - ), - forMember( - (destination) => destination.sunTime, - mapFrom((source) => - this.frequencyNormaliser.scheduleResolver(source, Day.sun), - ), - ), - ); - }; - } -} diff --git a/src/modules/ad/mappers/address.profile.ts b/src/modules/ad/mappers/address.profile.ts deleted file mode 100644 index 09fa720..0000000 --- a/src/modules/ad/mappers/address.profile.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Mapper, createMap } from '@automapper/core'; -import { AutomapperProfile, InjectMapper } from '@automapper/nestjs'; -import { Injectable } from '@nestjs/common'; -import { AddressDTO } from '../domain/dtos/address.dto'; -import { Address } from '../domain/entities/address'; - -@Injectable() -export class AdProfile extends AutomapperProfile { - constructor(@InjectMapper() mapper: Mapper) { - super(mapper); - } - - override get profile() { - return (mapper) => { - createMap(mapper, AddressDTO, Address); - }; - } -} diff --git a/src/modules/ad/queries/find-ad-by-uuid.query.ts b/src/modules/ad/queries/find-ad-by-uuid.query.ts deleted file mode 100644 index 32e0257..0000000 --- a/src/modules/ad/queries/find-ad-by-uuid.query.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { FindAdByUuidRequest } from '../domain/dtos/find-ad-by-uuid.request'; - -export class FindAdByUuidQuery { - readonly uuid: string; - - constructor(findAdByUuidRequest: FindAdByUuidRequest) { - this.uuid = findAdByUuidRequest.uuid; - } -} diff --git a/src/modules/ad/tests/integration/ad.repository.spec.ts b/src/modules/ad/tests/integration/ad.repository.spec.ts index 06cbc5d..ffd1da3 100644 --- a/src/modules/ad/tests/integration/ad.repository.spec.ts +++ b/src/modules/ad/tests/integration/ad.repository.spec.ts @@ -1,14 +1,22 @@ +import { PrismaService } from '@libs/db/prisma.service'; +import { + AD_REPOSITORY, + PARAMS_PROVIDER, + TIMEZONE_FINDER, + TIME_CONVERTER, +} from '@modules/ad/ad.di-tokens'; +import { AdMapper } from '@modules/ad/ad.mapper'; +import { AdRepository } from '@modules/ad/infrastructure/ad.repository'; +import { DefaultParamsProvider } from '@modules/ad/infrastructure/default-params-provider'; +import { TimeConverter } from '@modules/ad/infrastructure/time-converter'; +import { TimezoneFinder } from '@modules/ad/infrastructure/timezone-finder'; +import { ConfigModule } from '@nestjs/config'; +import { EventEmitterModule } from '@nestjs/event-emitter'; import { Test } from '@nestjs/testing'; -import { PrismaService } from '../../../database/adapters/secondaries/prisma-service'; -import { AdsRepository } from '../../adapters/secondaries/ads.repository'; -import { DatabaseModule } from '../../../database/database.module'; -import { Frequency } from '../../domain/types/frequency.enum'; -import { AdCreation } from '../../domain/dtos/ad.creation'; -import { Address } from '../../domain/entities/address'; describe('Ad Repository', () => { let prismaService: PrismaService; - let adsRepository: AdsRepository; + let adRepository: AdRepository; const executeInsertCommand = async (table: string, object: any) => { const command = `INSERT INTO ${table} ("${Object.keys(object).join( @@ -20,13 +28,14 @@ describe('Ad Repository', () => { const getSeed = (index: number, uuid: string): string => { return `'${uuid.slice(0, -2)}${index.toString(16).padStart(2, '0')}'`; }; + const baseUuid = { uuid: 'be459a29-7a41-4c0b-b371-abe90bfb6f00', }; - const baseAdress0Uuid = { + const baseOriginWaypointUuid = { uuid: 'bad5e786-3b15-4e51-a8fc-926fa9327ff1', }; - const baseAdress1Uuid = { + const baseDestinationWaypointUuid = { uuid: '4d200eb6-7389-487f-a1ca-dbc0e40381c9', }; const baseUserUuid = { @@ -35,24 +44,24 @@ describe('Ad Repository', () => { const driverAd = { driver: 'true', passenger: 'false', - seatsDriver: 3, - seatsPassenger: 0, - strict: 'false', - }; - const passengerAd = { - driver: 'false', - passenger: 'true', - seatsDriver: 0, - seatsPassenger: 1, - strict: 'false', - }; - const driverAndPassengerAd = { - driver: 'true', - passenger: 'true', - seatsDriver: 3, - seatsPassenger: 1, + seatsProposed: 3, + seatsRequested: 0, strict: 'false', }; + // const passengerAd = { + // driver: 'false', + // passenger: 'true', + // seatsProposed: 0, + // seatsRequested: 1, + // strict: 'false', + // }; + // const driverAndPassengerAd = { + // driver: 'true', + // passenger: 'true', + // seatsProposed: 3, + // seatsRequested: 1, + // strict: 'false', + // }; const punctualAd = { frequency: `'PUNCTUAL'`, fromDate: `'2023-01-01'`, @@ -63,7 +72,7 @@ describe('Ad Repository', () => { thuTime: 'NULL', friTime: 'NULL', satTime: 'NULL', - sunTime: `'07:00'`, + sunTime: `'2023-01-01T07:00:00Z'`, monMargin: 900, tueMargin: 900, wedMargin: 900, @@ -72,27 +81,26 @@ describe('Ad Repository', () => { satMargin: 900, sunMargin: 900, }; - const recurrentAd = { - frequency: `'RECURRENT'`, - fromDate: `'2023-01-01'`, - toDate: `'2023-12-31'`, - monTime: `'07:00'`, - tueTime: `'07:00'`, - wedTime: `'07:00'`, - thuTime: `'07:00'`, - friTime: `'07:00'`, - satTime: 'NULL', - sunTime: 'NULL', - monMargin: 900, - tueMargin: 900, - wedMargin: 900, - thuMargin: 900, - friMargin: 900, - satMargin: 900, - sunMargin: 900, - }; - - const address0 = { + // const recurrentAd = { + // frequency: `'RECURRENT'`, + // fromDate: `'2023-01-01'`, + // toDate: `'2023-12-31'`, + // monTime: `'2023-01-01T07:15:00Z'`, + // tueTime: `'2023-01-01T07:15:00Z'`, + // wedTime: `'2023-01-01T07:05:00Z'`, + // thuTime: `'2023-01-01T07:15:00Z'`, + // friTime: `'2023-01-01T07:15:00Z'`, + // satTime: 'NULL', + // sunTime: 'NULL', + // monMargin: 900, + // tueMargin: 900, + // wedMargin: 900, + // thuMargin: 900, + // friMargin: 900, + // satMargin: 900, + // sunMargin: 900, + // }; + const originWaypoint = { position: 0, lon: 43.7102, lat: 7.262, @@ -100,7 +108,7 @@ describe('Ad Repository', () => { postalCode: "'06000'", country: "'France'", }; - const address1 = { + const destinationWaypoint = { position: 1, lon: 43.2965, lat: 5.3698, @@ -108,6 +116,7 @@ describe('Ad Repository', () => { postalCode: "'13000'", country: "'France'", }; + const createPunctualDriverAds = async (nbToCreate = 10) => { const adToCreate = { ...baseUuid, @@ -118,139 +127,163 @@ describe('Ad Repository', () => { for (let i = 0; i < nbToCreate; i++) { adToCreate.uuid = getSeed(i, baseUuid.uuid); await executeInsertCommand('ad', adToCreate); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress0Uuid.uuid), + await executeInsertCommand('waypoint', { + uuid: getSeed(i, baseOriginWaypointUuid.uuid), adUuid: adToCreate.uuid, - ...address0, + ...originWaypoint, }); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress1Uuid.uuid), + await executeInsertCommand('waypoint', { + uuid: getSeed(i, baseDestinationWaypointUuid.uuid), adUuid: adToCreate.uuid, - ...address1, - }); - } - }; - const createRecurrentDriverAds = async (nbToCreate = 10) => { - const adToCreate = { - ...baseUuid, - ...baseUserUuid, - ...driverAd, - ...punctualAd, - }; - for (let i = 0; i < nbToCreate; i++) { - adToCreate.uuid = getSeed(i, baseUuid.uuid); - await executeInsertCommand('ad', adToCreate); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress0Uuid.uuid), - adUuid: adToCreate.uuid, - ...address0, - }); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress1Uuid.uuid), - adUuid: adToCreate.uuid, - ...address1, + ...destinationWaypoint, }); } }; - const createPunctualPassengerAds = async (nbToCreate = 10) => { - const adToCreate = { - ...baseUuid, - ...baseUserUuid, - ...passengerAd, - ...punctualAd, - }; - for (let i = 0; i < nbToCreate; i++) { - adToCreate.uuid = getSeed(i, baseUuid.uuid); - await executeInsertCommand('ad', adToCreate); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress0Uuid.uuid), - adUuid: adToCreate.uuid, - ...address0, - }); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress1Uuid.uuid), - adUuid: adToCreate.uuid, - ...address1, - }); - } - }; + // const createRecurrentDriverAds = async (nbToCreate = 10) => { + // const adToCreate = { + // ...baseUuid, + // ...baseUserUuid, + // ...driverAd, + // ...punctualAd, + // }; + // for (let i = 0; i < nbToCreate; i++) { + // adToCreate.uuid = getSeed(i, baseUuid.uuid); + // await executeInsertCommand('ad', adToCreate); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseOriginWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...originWaypoint, + // }); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseDestinationWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...destinationWaypoint, + // }); + // } + // }; - const createRecurrentPassengerAds = async (nbToCreate = 10) => { - const adToCreate = { - ...baseUuid, - ...baseUserUuid, - ...passengerAd, - ...recurrentAd, - }; - for (let i = 0; i < nbToCreate; i++) { - adToCreate.uuid = getSeed(i, baseUuid.uuid); - await executeInsertCommand('ad', adToCreate); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress0Uuid.uuid), - adUuid: adToCreate.uuid, - ...address0, - }); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress1Uuid.uuid), - adUuid: adToCreate.uuid, - ...address1, - }); - } - }; + // const createPunctualPassengerAds = async (nbToCreate = 10) => { + // const adToCreate = { + // ...baseUuid, + // ...baseUserUuid, + // ...passengerAd, + // ...punctualAd, + // }; + // for (let i = 0; i < nbToCreate; i++) { + // adToCreate.uuid = getSeed(i, baseUuid.uuid); + // await executeInsertCommand('ad', adToCreate); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseOriginWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...originWaypoint, + // }); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseDestinationWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...destinationWaypoint, + // }); + // } + // }; - const createPunctualDriverPassengerAds = async (nbToCreate = 10) => { - const adToCreate = { - ...baseUuid, - ...baseUserUuid, - ...driverAndPassengerAd, - ...punctualAd, - }; - for (let i = 0; i < nbToCreate; i++) { - adToCreate.uuid = getSeed(i, baseUuid.uuid); - await executeInsertCommand('ad', adToCreate); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress0Uuid.uuid), - adUuid: adToCreate.uuid, - ...address0, - }); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress1Uuid.uuid), - adUuid: adToCreate.uuid, - ...address1, - }); - } - }; + // const createRecurrentPassengerAds = async (nbToCreate = 10) => { + // const adToCreate = { + // ...baseUuid, + // ...baseUserUuid, + // ...passengerAd, + // ...recurrentAd, + // }; + // for (let i = 0; i < nbToCreate; i++) { + // adToCreate.uuid = getSeed(i, baseUuid.uuid); + // await executeInsertCommand('ad', adToCreate); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseOriginWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...originWaypoint, + // }); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseDestinationWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...destinationWaypoint, + // }); + // } + // }; + + // const createPunctualDriverPassengerAds = async (nbToCreate = 10) => { + // const adToCreate = { + // ...baseUuid, + // ...baseUserUuid, + // ...driverAndPassengerAd, + // ...punctualAd, + // }; + // for (let i = 0; i < nbToCreate; i++) { + // adToCreate.uuid = getSeed(i, baseUuid.uuid); + // await executeInsertCommand('ad', adToCreate); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseOriginWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...originWaypoint, + // }); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseDestinationWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...destinationWaypoint, + // }); + // } + // }; + + // const createRecurrentDriverPassengerAds = async (nbToCreate = 10) => { + // const adToCreate = { + // ...baseUuid, + // ...baseUserUuid, + // ...driverAndPassengerAd, + // ...recurrentAd, + // }; + // for (let i = 0; i < nbToCreate; i++) { + // adToCreate.uuid = getSeed(i, baseUuid.uuid); + // await executeInsertCommand('ad', adToCreate); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseOriginWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...originWaypoint, + // }); + // await executeInsertCommand('waypoint', { + // uuid: getSeed(i, baseDestinationWaypointUuid.uuid), + // adUuid: adToCreate.uuid, + // ...destinationWaypoint, + // }); + // } + // }; - const createRecurrentDriverPassengerAds = async (nbToCreate = 10) => { - const adToCreate = { - ...baseUuid, - ...baseUserUuid, - ...driverAndPassengerAd, - ...recurrentAd, - }; - for (let i = 0; i < nbToCreate; i++) { - adToCreate.uuid = getSeed(i, baseUuid.uuid); - await executeInsertCommand('ad', adToCreate); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress0Uuid.uuid), - adUuid: adToCreate.uuid, - ...address0, - }); - await executeInsertCommand('address', { - uuid: getSeed(i, baseAdress1Uuid.uuid), - adUuid: adToCreate.uuid, - ...address1, - }); - } - }; beforeAll(async () => { const module = await Test.createTestingModule({ - imports: [DatabaseModule], - providers: [PrismaService, AdsRepository], + imports: [ + EventEmitterModule.forRoot(), + ConfigModule.forRoot({ isGlobal: true }), + ], + providers: [ + PrismaService, + AdMapper, + { + provide: AD_REPOSITORY, + useClass: AdRepository, + }, + { + provide: PARAMS_PROVIDER, + useClass: DefaultParamsProvider, + }, + { + provide: TIMEZONE_FINDER, + useClass: TimezoneFinder, + }, + { + provide: TIME_CONVERTER, + useClass: TimeConverter, + }, + ], }).compile(); prismaService = module.get(PrismaService); - adsRepository = module.get(AdsRepository); + adRepository = module.get(AD_REPOSITORY); }); afterAll(async () => { await prismaService.$disconnect(); @@ -259,210 +292,212 @@ describe('Ad Repository', () => { beforeEach(async () => { await prismaService.ad.deleteMany(); }); - describe('findAll', () => { - it('should return an empty data array', async () => { - const res = await adsRepository.findAll(); - expect(res).toEqual({ - data: [], - total: 0, - }); - }); + // describe('findAll', () => { + // it('should return an empty data array', async () => { + // const res = await adRepository.findAll(); + // expect(res).toEqual({ + // data: [], + // total: 0, + // }); + // }); - describe('drivers', () => { - it('should return a data array with 8 punctual driver ads', async () => { - await createPunctualDriverAds(8); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(8); - expect(ads.total).toBe(8); - expect(ads.data[0].driver).toBeTruthy(); - expect(ads.data[0].passenger).toBeFalsy(); - }); + // describe('drivers', () => { + // it('should return a data array with 8 punctual driver ads', async () => { + // await createPunctualDriverAds(8); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(8); + // expect(ads.total).toBe(8); + // expect(ads.data[0].driver).toBeTruthy(); + // expect(ads.data[0].passenger).toBeFalsy(); + // }); - it('should return a data array limited to 10 punctual driver ads', async () => { - await createPunctualDriverAds(20); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(10); - expect(ads.total).toBe(20); - expect(ads.data[1].driver).toBeTruthy(); - expect(ads.data[1].passenger).toBeFalsy(); - }); + // it('should return a data array limited to 10 punctual driver ads', async () => { + // await createPunctualDriverAds(20); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(10); + // expect(ads.total).toBe(20); + // expect(ads.data[1].driver).toBeTruthy(); + // expect(ads.data[1].passenger).toBeFalsy(); + // }); - it('should return a data array with 8 recurrent driver ads', async () => { - await createRecurrentDriverAds(8); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(8); - expect(ads.total).toBe(8); - expect(ads.data[2].driver).toBeTruthy(); - expect(ads.data[2].passenger).toBeFalsy(); - }); + // it('should return a data array with 8 recurrent driver ads', async () => { + // await createRecurrentDriverAds(8); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(8); + // expect(ads.total).toBe(8); + // expect(ads.data[2].driver).toBeTruthy(); + // expect(ads.data[2].passenger).toBeFalsy(); + // }); - it('should return a data array limited to 10 recurrent driver ads', async () => { - await createRecurrentDriverAds(20); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(10); - expect(ads.total).toBe(20); - expect(ads.data[3].driver).toBeTruthy(); - expect(ads.data[3].passenger).toBeFalsy(); - }); - }); + // it('should return a data array limited to 10 recurrent driver ads', async () => { + // await createRecurrentDriverAds(20); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(10); + // expect(ads.total).toBe(20); + // expect(ads.data[3].driver).toBeTruthy(); + // expect(ads.data[3].passenger).toBeFalsy(); + // }); + // }); - describe('passengers', () => { - it('should return a data array with 7 punctual passenger ads', async () => { - await createPunctualPassengerAds(7); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(7); - expect(ads.total).toBe(7); - expect(ads.data[0].passenger).toBeTruthy(); - expect(ads.data[0].driver).toBeFalsy(); - }); + // describe('passengers', () => { + // it('should return a data array with 7 punctual passenger ads', async () => { + // await createPunctualPassengerAds(7); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(7); + // expect(ads.total).toBe(7); + // expect(ads.data[0].passenger).toBeTruthy(); + // expect(ads.data[0].driver).toBeFalsy(); + // }); - it('should return a data array limited to 10 punctual passenger ads', async () => { - await createPunctualPassengerAds(15); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(10); - expect(ads.total).toBe(15); - expect(ads.data[1].passenger).toBeTruthy(); - expect(ads.data[1].driver).toBeFalsy(); - }); + // it('should return a data array limited to 10 punctual passenger ads', async () => { + // await createPunctualPassengerAds(15); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(10); + // expect(ads.total).toBe(15); + // expect(ads.data[1].passenger).toBeTruthy(); + // expect(ads.data[1].driver).toBeFalsy(); + // }); - it('should return a data array with 7 recurrent passenger ads', async () => { - await createRecurrentPassengerAds(7); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(7); - expect(ads.total).toBe(7); - expect(ads.data[2].passenger).toBeTruthy(); - expect(ads.data[2].driver).toBeFalsy(); - }); + // it('should return a data array with 7 recurrent passenger ads', async () => { + // await createRecurrentPassengerAds(7); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(7); + // expect(ads.total).toBe(7); + // expect(ads.data[2].passenger).toBeTruthy(); + // expect(ads.data[2].driver).toBeFalsy(); + // }); - it('should return a data array limited to 10 recurrent passenger ads', async () => { - await createRecurrentPassengerAds(15); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(10); - expect(ads.total).toBe(15); - expect(ads.data[3].passenger).toBeTruthy(); - expect(ads.data[3].driver).toBeFalsy(); - }); - }); + // it('should return a data array limited to 10 recurrent passenger ads', async () => { + // await createRecurrentPassengerAds(15); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(10); + // expect(ads.total).toBe(15); + // expect(ads.data[3].passenger).toBeTruthy(); + // expect(ads.data[3].driver).toBeFalsy(); + // }); + // }); - describe('drivers and passengers', () => { - it('should return a data array with 6 punctual driver and passenger ads', async () => { - await createPunctualDriverPassengerAds(6); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(6); - expect(ads.total).toBe(6); - expect(ads.data[0].passenger).toBeTruthy(); - expect(ads.data[0].driver).toBeTruthy(); - }); + // describe('drivers and passengers', () => { + // it('should return a data array with 6 punctual driver and passenger ads', async () => { + // await createPunctualDriverPassengerAds(6); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(6); + // expect(ads.total).toBe(6); + // expect(ads.data[0].passenger).toBeTruthy(); + // expect(ads.data[0].driver).toBeTruthy(); + // }); - it('should return a data array limited to 10 punctual driver and passenger ads', async () => { - await createPunctualDriverPassengerAds(16); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(10); - expect(ads.total).toBe(16); - expect(ads.data[1].passenger).toBeTruthy(); - expect(ads.data[1].driver).toBeTruthy(); - }); + // it('should return a data array limited to 10 punctual driver and passenger ads', async () => { + // await createPunctualDriverPassengerAds(16); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(10); + // expect(ads.total).toBe(16); + // expect(ads.data[1].passenger).toBeTruthy(); + // expect(ads.data[1].driver).toBeTruthy(); + // }); - it('should return a data array with 6 recurrent driver and passenger ads', async () => { - await createRecurrentDriverPassengerAds(6); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(6); - expect(ads.total).toBe(6); - expect(ads.data[2].passenger).toBeTruthy(); - expect(ads.data[2].driver).toBeTruthy(); - }); + // it('should return a data array with 6 recurrent driver and passenger ads', async () => { + // await createRecurrentDriverPassengerAds(6); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(6); + // expect(ads.total).toBe(6); + // expect(ads.data[2].passenger).toBeTruthy(); + // expect(ads.data[2].driver).toBeTruthy(); + // }); - it('should return a data array limited to 10 recurrent driver and passenger ads', async () => { - await createRecurrentDriverPassengerAds(16); - const ads = await adsRepository.findAll(); - expect(ads.data.length).toBe(10); - expect(ads.total).toBe(16); - expect(ads.data[3].passenger).toBeTruthy(); - expect(ads.data[3].driver).toBeTruthy(); - }); - }); - }); - describe('findOneByUuid', () => { + // it('should return a data array limited to 10 recurrent driver and passenger ads', async () => { + // await createRecurrentDriverPassengerAds(16); + // const ads = await adRepository.findAll(); + // expect(ads.data.length).toBe(10); + // expect(ads.total).toBe(16); + // expect(ads.data[3].passenger).toBeTruthy(); + // expect(ads.data[3].driver).toBeTruthy(); + // }); + // }); + // }); + describe('findOneById', () => { it('should return an ad', async () => { await createPunctualDriverAds(1); - const ad = await adsRepository.findOneByUuid(baseUuid.uuid); + const result = await adRepository.findOneById(baseUuid.uuid, { + waypoints: true, + }); - expect(ad.uuid).toBe(baseUuid.uuid); + expect(result.id).toBe(baseUuid.uuid); }); - it('should return null', async () => { - const ad = await adsRepository.findOneByUuid( - '544572be-11fb-4244-8235-587221fc9104', - ); - expect(ad).toBeNull(); - }); + // it('should return null', async () => { + // const ad = await adRepository.findOneById( + // '544572be-11fb-4244-8235-587221fc9104', + // ); + // expect(ad).toBeNull(); + // }); }); - describe('create', () => { - it('should create an punctual ad', async () => { - const beforeCount = await prismaService.ad.count(); - const adToCreate: AdCreation = new AdCreation(); + // describe('create', () => { + // it('should create a punctual ad', async () => { + // const beforeCount = await prismaService.ad.count(); + // const adToCreate: AdDTO = new AdDTO(); - adToCreate.uuid = 'be459a29-7a41-4c0b-b371-abe90bfb6f00'; - adToCreate.userUuid = '4e52b54d-a729-4dbd-9283-f84a11bb2200'; - adToCreate.driver = true; - adToCreate.passenger = false; - adToCreate.frequency = Frequency.PUNCTUAL; - adToCreate.fromDate = new Date('05-22-2023 09:36'); - adToCreate.toDate = new Date('05-22-2023 09:36'); - adToCreate.monTime = '09:36'; - adToCreate.monMargin = 900; - adToCreate.tueMargin = 900; - adToCreate.wedMargin = 900; - adToCreate.thuMargin = 900; - adToCreate.friMargin = 900; - adToCreate.satMargin = 900; - adToCreate.sunMargin = 900; - adToCreate.seatsDriver = 3; - adToCreate.seatsPassenger = 0; - adToCreate.strict = false; - adToCreate.addresses = { - create: [address0 as Address, address1 as Address], - }; - const ad = await adsRepository.create(adToCreate); + // adToCreate.uuid = 'be459a29-7a41-4c0b-b371-abe90bfb6f00'; + // adToCreate.userUuid = '4e52b54d-a729-4dbd-9283-f84a11bb2200'; + // adToCreate.driver = true; + // adToCreate.passenger = false; + // adToCreate.frequency = Frequency.PUNCTUAL; + // adToCreate.fromDate = new Date('05-22-2023 09:36'); + // adToCreate.toDate = new Date('05-22-2023 09:36'); + // adToCreate.monTime = '09:36'; + // adToCreate.monMargin = 900; + // adToCreate.tueMargin = 900; + // adToCreate.wedMargin = 900; + // adToCreate.thuMargin = 900; + // adToCreate.friMargin = 900; + // adToCreate.satMargin = 900; + // adToCreate.sunMargin = 900; + // adToCreate.seatsProposed = 3; + // adToCreate.seatsRequested = 0; + // adToCreate.strict = false; + // adToCreate.waypoints = { + // create: [originWaypoint as Waypoint, destinationWaypoint as Waypoint], + // }; + // const ad = await adRepository.create(adToCreate); - const afterCount = await prismaService.ad.count(); + // const afterCount = await prismaService.ad.count(); - expect(afterCount - beforeCount).toBe(1); - expect(ad.uuid).toBe('be459a29-7a41-4c0b-b371-abe90bfb6f00'); - }); - it('should create an recurrent ad', async () => { - const beforeCount = await prismaService.ad.count(); - const adToCreate: AdCreation = new AdCreation(); + // expect(afterCount - beforeCount).toBe(1); + // expect(ad.uuid).toBe('be459a29-7a41-4c0b-b371-abe90bfb6f00'); + // }); + // it('should create a recurrent ad', async () => { + // const beforeCount = await prismaService.ad.count(); + // const adToCreate: AdDTO = new AdDTO(); - adToCreate.uuid = '137a26fa-4b38-48ba-aecf-1a75f6b20f3d'; - adToCreate.userUuid = '4e52b54d-a729-4dbd-9283-f84a11bb2200'; - adToCreate.driver = true; - adToCreate.passenger = false; - adToCreate.frequency = Frequency.RECURRENT; - adToCreate.fromDate = new Date('01-15-2023 '); - adToCreate.toDate = new Date('10-31-2023'); - adToCreate.monTime = '07:30'; - adToCreate.friTime = '07:45'; - adToCreate.thuTime = '08:00'; - adToCreate.monMargin = 900; - adToCreate.tueMargin = 900; - adToCreate.wedMargin = 900; - adToCreate.thuMargin = 900; - adToCreate.friMargin = 900; - adToCreate.satMargin = 900; - adToCreate.sunMargin = 900; - adToCreate.seatsDriver = 2; - adToCreate.seatsPassenger = 0; - adToCreate.strict = false; - adToCreate.addresses = { - create: [address0 as Address, address1 as Address], - }; - const ad = await adsRepository.create(adToCreate); + // adToCreate.uuid = '137a26fa-4b38-48ba-aecf-1a75f6b20f3d'; + // adToCreate.userUuid = '4e52b54d-a729-4dbd-9283-f84a11bb2200'; + // adToCreate.driver = true; + // adToCreate.passenger = false; + // adToCreate.frequency = Frequency.RECURRENT; + // adToCreate.fromDate = new Date('01-15-2023 '); + // adToCreate.toDate = new Date('10-31-2023'); + // adToCreate.monTime = '07:30'; + // adToCreate.friTime = '07:45'; + // adToCreate.thuTime = '08:00'; + // adToCreate.monMargin = 900; + // adToCreate.tueMargin = 900; + // adToCreate.wedMargin = 900; + // adToCreate.thuMargin = 900; + // adToCreate.friMargin = 900; + // adToCreate.satMargin = 900; + // adToCreate.sunMargin = 900; + // adToCreate.seatsProposed = 2; + // adToCreate.seatsRequested = 0; + // adToCreate.strict = false; + // adToCreate.waypoints = { + // create: [originWaypoint as Waypoint, destinationWaypoint as Waypoint], + // }; + // const ad = await adRepository.create(adToCreate); - const afterCount = await prismaService.ad.count(); + // const afterCount = await prismaService.ad.count(); - expect(afterCount - beforeCount).toBe(1); - expect(ad.uuid).toBe('137a26fa-4b38-48ba-aecf-1a75f6b20f3d'); - }); - }); + // expect(afterCount - beforeCount).toBe(1); + // expect(ad.uuid).toBe('137a26fa-4b38-48ba-aecf-1a75f6b20f3d'); + // }); + // }); }); diff --git a/src/modules/ad/tests/unit/ad.mapper.spec.ts b/src/modules/ad/tests/unit/ad.mapper.spec.ts new file mode 100644 index 0000000..2a6a13d --- /dev/null +++ b/src/modules/ad/tests/unit/ad.mapper.spec.ts @@ -0,0 +1,221 @@ +import { + PARAMS_PROVIDER, + TIMEZONE_FINDER, + TIME_CONVERTER, +} from '@modules/ad/ad.di-tokens'; +import { AdMapper } from '@modules/ad/ad.mapper'; +import { AdEntity } from '@modules/ad/core/ad.entity'; +import { Frequency } from '@modules/ad/core/ad.types'; +import { DefaultParamsProviderPort } from '@modules/ad/core/ports/default-params-provider.port'; +import { TimeConverterPort } from '@modules/ad/core/ports/time-converter.port'; +import { TimezoneFinderPort } from '@modules/ad/core/ports/timezone-finder.port'; +import { + AdReadModel, + AdWriteModel, +} from '@modules/ad/infrastructure/ad.repository'; +import { AdResponseDto } from '@modules/ad/interface/dtos/ad.response.dto'; +import { Test } from '@nestjs/testing'; + +const now = new Date('2023-06-21 06:00:00'); +const adEntity: AdEntity = new AdEntity({ + id: 'c160cf8c-f057-4962-841f-3ad68346df44', + props: { + userId: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e', + driver: false, + passenger: true, + frequency: Frequency.PUNCTUAL, + fromDate: '2023-06-21', + toDate: '2023-06-21', + schedule: { + mon: '07:15', + tue: '07:15', + wed: '07:15', + thu: '07:15', + fri: '07:15', + sat: '07:15', + sun: '07:15', + }, + waypoints: [ + { + position: 0, + address: { + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + coordinates: { + lat: 48.689445, + lon: 6.1765102, + }, + }, + }, + { + position: 1, + address: { + locality: 'Paris', + postalCode: '75000', + country: 'France', + coordinates: { + lat: 48.8566, + lon: 2.3522, + }, + }, + }, + ], + marginDurations: { + mon: 600, + tue: 600, + wed: 600, + thu: 600, + fri: 600, + sat: 600, + sun: 600, + }, + strict: false, + seatsProposed: 3, + seatsRequested: 1, + }, + createdAt: now, + updatedAt: now, +}); +const adReadModel: AdReadModel = { + uuid: 'c160cf8c-f057-4962-841f-3ad68346df44', + userUuid: '7ca2490b-d04d-4ac5-8d6c-5c416fab922e', + driver: false, + passenger: true, + frequency: Frequency.PUNCTUAL, + fromDate: new Date('2023-06-21'), + toDate: new Date('2023-06-21'), + monTime: undefined, + tueTime: undefined, + wedTime: new Date('2023-06-21T07:15:00Z'), + thuTime: undefined, + friTime: undefined, + satTime: undefined, + sunTime: undefined, + waypoints: [ + { + uuid: '6f53f55e-2bdb-4c23-b6a9-6d7b498e47b9', + position: 0, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + lat: 48.689445, + lon: 6.1765102, + createdAt: now, + updatedAt: now, + }, + { + uuid: 'e18c6a84-0ab7-4e44-af1d-829d0b0d0573', + position: 1, + locality: 'Paris', + postalCode: '75000', + country: 'France', + lat: 48.8566, + lon: 2.3522, + createdAt: now, + updatedAt: now, + }, + ], + monMargin: 600, + tueMargin: 600, + wedMargin: 600, + thuMargin: 600, + friMargin: 600, + satMargin: 600, + sunMargin: 600, + strict: false, + seatsProposed: 3, + seatsRequested: 1, + createdAt: now, + updatedAt: now, +}; + +const mockDefaultParamsProvider: DefaultParamsProviderPort = { + getParams: () => { + return { + MON_MARGIN: 900, + TUE_MARGIN: 900, + WED_MARGIN: 900, + THU_MARGIN: 900, + FRI_MARGIN: 900, + SAT_MARGIN: 900, + SUN_MARGIN: 900, + DRIVER: false, + SEATS_PROPOSED: 3, + PASSENGER: true, + SEATS_REQUESTED: 1, + STRICT: false, + DEFAULT_TIMEZONE: 'Europe/Paris', + }; + }, +}; + +const mockTimezoneFinder: TimezoneFinderPort = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + timezones: jest.fn().mockImplementation((lon: number, lat: number) => { + if (lon < 60) return 'Europe/Paris'; + return 'America/New_York'; + }), +}; + +const mockTimeConverter: TimeConverterPort = { + localDateTimeToUtc: jest + .fn() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementation((datetime: Date, timezone: string, dst?: boolean) => { + return datetime; + }), + utcDatetimeToLocalTime: jest.fn(), +}; + +describe('Ad Mapper', () => { + let adMapper: AdMapper; + + beforeAll(async () => { + const module = await Test.createTestingModule({ + providers: [ + AdMapper, + { + provide: PARAMS_PROVIDER, + useValue: mockDefaultParamsProvider, + }, + { + provide: TIMEZONE_FINDER, + useValue: mockTimezoneFinder, + }, + { + provide: TIME_CONVERTER, + useValue: mockTimeConverter, + }, + ], + }).compile(); + adMapper = module.get(AdMapper); + }); + + it('should be defined', () => { + expect(adMapper).toBeDefined(); + }); + + it('should map domain entity to persistence data', async () => { + const mapped: AdWriteModel = adMapper.toPersistence(adEntity); + expect(mapped.waypoints.create[0].uuid.length).toBe(36); + expect(mapped.waypoints.create[1].uuid.length).toBe(36); + }); + + it('should map persisted data to domain entity', async () => { + const mapped: AdEntity = adMapper.toDomain(adReadModel); + expect(mapped.getProps().waypoints[0].address.coordinates.lat).toBe( + 48.689445, + ); + expect(mapped.getProps().waypoints[1].address.coordinates.lon).toBe(2.3522); + }); + + it('should map domain entity to response', async () => { + const mapped: AdResponseDto = adMapper.toResponse(adEntity); + expect(mapped.id).toBe('c160cf8c-f057-4962-841f-3ad68346df44'); + }); +}); diff --git a/src/modules/ad/tests/unit/core/ad.entity.spec.ts b/src/modules/ad/tests/unit/core/ad.entity.spec.ts new file mode 100644 index 0000000..8d71ac6 --- /dev/null +++ b/src/modules/ad/tests/unit/core/ad.entity.spec.ts @@ -0,0 +1,409 @@ +import { AdEntity } from '@modules/ad/core/ad.entity'; +import { + CreateAdProps, + DefaultAdProps, + Frequency, +} from '@modules/ad/core/ad.types'; +import { MarginDurationsProps } from '@modules/ad/core/value-objects/margin-durations.value-object'; +import { WaypointProps } from '@modules/ad/core/value-objects/waypoint.value-object'; + +const originWaypointProps: WaypointProps = { + position: 0, + address: { + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + coordinates: { + lon: 48.689445, + lat: 6.17651, + }, + }, +}; +const destinationWaypointProps: WaypointProps = { + position: 1, + address: { + locality: 'Paris', + postalCode: '75000', + country: 'France', + coordinates: { + lon: 48.8566, + lat: 2.3522, + }, + }, +}; +const marginDurationsProps: MarginDurationsProps = { + mon: 600, + tue: 600, + wed: 600, + thu: 600, + fri: 600, + sat: 600, + sun: 600, +}; +const baseCreateAdProps = { + userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', + seatsProposed: 3, + seatsRequested: 1, + strict: false, + waypoints: [originWaypointProps, destinationWaypointProps], +}; +const punctualCreateAdProps = { + fromDate: '2023-06-21', + toDate: '2023-06-21', + schedule: { + wed: '08:30', + }, + frequency: Frequency.PUNCTUAL, +}; +const recurrentCreateAdProps = { + fromDate: '2023-06-21', + toDate: '2024-06-20', + schedule: { + mon: '08:30', + tue: '08:30', + wed: '08:00', + thu: '08:30', + fri: '08:30', + }, + frequency: Frequency.RECURRENT, +}; +const punctualPassengerCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: false, + passenger: true, +}; +const recurrentPassengerCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...recurrentCreateAdProps, + marginDurations: marginDurationsProps, + driver: false, + passenger: true, +}; +const punctualDriverCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: true, + passenger: false, +}; +const recurrentDriverCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...recurrentCreateAdProps, + marginDurations: marginDurationsProps, + driver: true, + passenger: false, +}; +const punctualDriverPassengerCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: true, + passenger: true, +}; +const recurrentDriverPassengerCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...recurrentCreateAdProps, + marginDurations: marginDurationsProps, + driver: true, + passenger: true, +}; +const defaultAdProps: DefaultAdProps = { + driver: false, + passenger: true, + marginDurations: { + mon: 900, + tue: 900, + wed: 900, + thu: 900, + fri: 900, + sat: 900, + sun: 900, + }, + seatsProposed: 3, + seatsRequested: 1, + strict: false, +}; + +describe('Ad entity create', () => { + describe('With complete props', () => { + it('should create a new punctual passenger ad entity', async () => { + const punctualPassengerAd: AdEntity = AdEntity.create( + punctualPassengerCreateAdProps, + defaultAdProps, + ); + expect(punctualPassengerAd.id.length).toBe(36); + expect(punctualPassengerAd.getProps().schedule.mon).toBeUndefined(); + expect(punctualPassengerAd.getProps().schedule.wed).toBe('08:30'); + expect(punctualPassengerAd.getProps().driver).toBeFalsy(); + expect(punctualPassengerAd.getProps().passenger).toBeTruthy(); + }); + it('should create a new punctual driver ad entity', async () => { + const punctualDriverAd: AdEntity = AdEntity.create( + punctualDriverCreateAdProps, + defaultAdProps, + ); + expect(punctualDriverAd.id.length).toBe(36); + expect(punctualDriverAd.getProps().schedule.mon).toBeUndefined(); + expect(punctualDriverAd.getProps().schedule.wed).toBe('08:30'); + expect(punctualDriverAd.getProps().driver).toBeTruthy(); + expect(punctualDriverAd.getProps().passenger).toBeFalsy(); + }); + it('should create a new punctual driver and passenger ad entity', async () => { + const punctualDriverPassengerAd: AdEntity = AdEntity.create( + punctualDriverPassengerCreateAdProps, + defaultAdProps, + ); + expect(punctualDriverPassengerAd.id.length).toBe(36); + expect(punctualDriverPassengerAd.getProps().schedule.mon).toBeUndefined(); + expect(punctualDriverPassengerAd.getProps().schedule.wed).toBe('08:30'); + expect(punctualDriverPassengerAd.getProps().driver).toBeTruthy(); + expect(punctualDriverPassengerAd.getProps().passenger).toBeTruthy(); + }); + it('should create a new recurrent passenger ad entity', async () => { + const recurrentPassengerAd: AdEntity = AdEntity.create( + recurrentPassengerCreateAdProps, + defaultAdProps, + ); + expect(recurrentPassengerAd.id.length).toBe(36); + expect(recurrentPassengerAd.getProps().schedule.mon).toBe('08:30'); + expect(recurrentPassengerAd.getProps().schedule.sat).toBeUndefined(); + expect(recurrentPassengerAd.getProps().driver).toBeFalsy(); + expect(recurrentPassengerAd.getProps().passenger).toBeTruthy(); + }); + it('should create a new recurrent driver ad entity', async () => { + const recurrentDriverAd: AdEntity = AdEntity.create( + recurrentDriverCreateAdProps, + defaultAdProps, + ); + expect(recurrentDriverAd.id.length).toBe(36); + expect(recurrentDriverAd.getProps().schedule.mon).toBe('08:30'); + expect(recurrentDriverAd.getProps().schedule.sat).toBeUndefined(); + expect(recurrentDriverAd.getProps().driver).toBeTruthy(); + expect(recurrentDriverAd.getProps().passenger).toBeFalsy(); + }); + it('should create a new recurrent driver and passenger ad entity', async () => { + const recurrentDriverPassengerAd: AdEntity = AdEntity.create( + recurrentDriverPassengerCreateAdProps, + defaultAdProps, + ); + expect(recurrentDriverPassengerAd.id.length).toBe(36); + expect(recurrentDriverPassengerAd.getProps().schedule.mon).toBe('08:30'); + expect( + recurrentDriverPassengerAd.getProps().schedule.sat, + ).toBeUndefined(); + expect(recurrentDriverPassengerAd.getProps().driver).toBeTruthy(); + expect(recurrentDriverPassengerAd.getProps().passenger).toBeTruthy(); + }); + }); + + describe('With incomplete props', () => { + it('should create a new punctual passenger ad entity if no role is given', async () => { + const punctualWithoutRoleCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: false, + passenger: false, + }; + const punctualWithoutRoleAd: AdEntity = AdEntity.create( + punctualWithoutRoleCreateAdProps, + defaultAdProps, + ); + expect(punctualWithoutRoleAd.id.length).toBe(36); + expect(punctualWithoutRoleAd.getProps().schedule.mon).toBeUndefined(); + expect(punctualWithoutRoleAd.getProps().schedule.wed).toBe('08:30'); + expect(punctualWithoutRoleAd.getProps().driver).toBeFalsy(); + expect(punctualWithoutRoleAd.getProps().passenger).toBeTruthy(); + }); + it('should create a new strict punctual passenger ad entity if no strict param is given', async () => { + const punctualWithoutStrictCreateAdProps: CreateAdProps = { + userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', + seatsProposed: 3, + seatsRequested: 1, + strict: undefined, + waypoints: [originWaypointProps, destinationWaypointProps], + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: false, + passenger: true, + }; + const punctualWithoutStrictAd: AdEntity = AdEntity.create( + punctualWithoutStrictCreateAdProps, + defaultAdProps, + ); + expect(punctualWithoutStrictAd.id.length).toBe(36); + expect(punctualWithoutStrictAd.getProps().schedule.mon).toBeUndefined(); + expect(punctualWithoutStrictAd.getProps().schedule.wed).toBe('08:30'); + expect(punctualWithoutStrictAd.getProps().driver).toBeFalsy(); + expect(punctualWithoutStrictAd.getProps().passenger).toBeTruthy(); + expect(punctualWithoutStrictAd.getProps().strict).toBeFalsy(); + }); + it('should create a new punctual passenger ad entity with seats requested if no seats requested param is given', async () => { + const punctualWithoutSeatsRequestedCreateAdProps: CreateAdProps = { + userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', + seatsProposed: 3, + seatsRequested: undefined, + strict: false, + waypoints: [originWaypointProps, destinationWaypointProps], + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: false, + passenger: true, + }; + const punctualWithoutSeatsRequestedAd: AdEntity = AdEntity.create( + punctualWithoutSeatsRequestedCreateAdProps, + defaultAdProps, + ); + expect(punctualWithoutSeatsRequestedAd.id.length).toBe(36); + expect( + punctualWithoutSeatsRequestedAd.getProps().schedule.mon, + ).toBeUndefined(); + expect(punctualWithoutSeatsRequestedAd.getProps().schedule.wed).toBe( + '08:30', + ); + expect(punctualWithoutSeatsRequestedAd.getProps().driver).toBeFalsy(); + expect(punctualWithoutSeatsRequestedAd.getProps().passenger).toBeTruthy(); + expect(punctualWithoutSeatsRequestedAd.getProps().seatsRequested).toBe(1); + }); + it('should create a new punctual driver ad entity with seats proposed if no seats proposed param is given', async () => { + const punctualWithoutSeatsProposedCreateAdProps: CreateAdProps = { + userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', + seatsProposed: undefined, + seatsRequested: 1, + strict: false, + waypoints: [originWaypointProps, destinationWaypointProps], + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: true, + passenger: false, + }; + const punctualWithoutSeatsProposedAd: AdEntity = AdEntity.create( + punctualWithoutSeatsProposedCreateAdProps, + defaultAdProps, + ); + expect(punctualWithoutSeatsProposedAd.id.length).toBe(36); + expect( + punctualWithoutSeatsProposedAd.getProps().schedule.mon, + ).toBeUndefined(); + expect(punctualWithoutSeatsProposedAd.getProps().schedule.wed).toBe( + '08:30', + ); + expect(punctualWithoutSeatsProposedAd.getProps().driver).toBeTruthy(); + expect(punctualWithoutSeatsProposedAd.getProps().passenger).toBeFalsy(); + expect(punctualWithoutSeatsProposedAd.getProps().seatsProposed).toBe(3); + }); + it('should create a new punctual driver ad entity with margin durations if margin durations are empty', async () => { + const punctualWithoutMarginDurationsCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + waypoints: [originWaypointProps, destinationWaypointProps], + ...punctualCreateAdProps, + marginDurations: {}, + driver: true, + passenger: false, + }; + const punctualWithoutMarginDurationsAd: AdEntity = AdEntity.create( + punctualWithoutMarginDurationsCreateAdProps, + defaultAdProps, + ); + expect(punctualWithoutMarginDurationsAd.id.length).toBe(36); + expect( + punctualWithoutMarginDurationsAd.getProps().schedule.mon, + ).toBeUndefined(); + expect(punctualWithoutMarginDurationsAd.getProps().schedule.wed).toBe( + '08:30', + ); + expect(punctualWithoutMarginDurationsAd.getProps().driver).toBeTruthy(); + expect(punctualWithoutMarginDurationsAd.getProps().passenger).toBeFalsy(); + expect( + punctualWithoutMarginDurationsAd.getProps().marginDurations.mon, + ).toBe(900); + }); + it('should create a new punctual driver ad entity with margin durations if margin durations are undefined', async () => { + const punctualWithoutMarginDurationsCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + waypoints: [originWaypointProps, destinationWaypointProps], + ...punctualCreateAdProps, + marginDurations: undefined, + driver: true, + passenger: false, + }; + const punctualWithoutMarginDurationsAd: AdEntity = AdEntity.create( + punctualWithoutMarginDurationsCreateAdProps, + defaultAdProps, + ); + expect(punctualWithoutMarginDurationsAd.id.length).toBe(36); + expect( + punctualWithoutMarginDurationsAd.getProps().schedule.mon, + ).toBeUndefined(); + expect(punctualWithoutMarginDurationsAd.getProps().schedule.wed).toBe( + '08:30', + ); + expect(punctualWithoutMarginDurationsAd.getProps().driver).toBeTruthy(); + expect(punctualWithoutMarginDurationsAd.getProps().passenger).toBeFalsy(); + expect( + punctualWithoutMarginDurationsAd.getProps().marginDurations.mon, + ).toBe(900); + }); + it('should create a new punctual passenger ad entity with valid positions if positions are missing', async () => { + const punctualWithoutPositionsCreateAdProps: CreateAdProps = { + userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', + seatsProposed: 3, + seatsRequested: 1, + strict: undefined, + waypoints: [ + { + position: undefined, + address: { + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + coordinates: { + lon: 48.689445, + lat: 6.17651, + }, + }, + }, + { + position: undefined, + address: { + locality: 'Paris', + postalCode: '75000', + country: 'France', + coordinates: { + lon: 48.8566, + lat: 2.3522, + }, + }, + }, + ], + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: false, + passenger: false, + }; + const punctualWithoutPositionsAd: AdEntity = AdEntity.create( + punctualWithoutPositionsCreateAdProps, + defaultAdProps, + ); + expect(punctualWithoutPositionsAd.id.length).toBe(36); + expect( + punctualWithoutPositionsAd.getProps().schedule.mon, + ).toBeUndefined(); + expect(punctualWithoutPositionsAd.getProps().schedule.wed).toBe('08:30'); + expect(punctualWithoutPositionsAd.getProps().driver).toBeFalsy(); + expect(punctualWithoutPositionsAd.getProps().passenger).toBeTruthy(); + expect(punctualWithoutPositionsAd.getProps().waypoints[0].position).toBe( + 0, + ); + expect(punctualWithoutPositionsAd.getProps().waypoints[1].position).toBe( + 1, + ); + }); + }); +}); diff --git a/src/modules/ad/tests/unit/core/address.value-object.spec.ts b/src/modules/ad/tests/unit/core/address.value-object.spec.ts new file mode 100644 index 0000000..5475680 --- /dev/null +++ b/src/modules/ad/tests/unit/core/address.value-object.spec.ts @@ -0,0 +1,24 @@ +import { Address } from '@modules/ad/core/value-objects/address.value-object'; + +describe('Address value object', () => { + it('should create an address value object', () => { + const addressVO = new Address({ + houseNumber: '5', + street: 'rue de la monnaie', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + coordinates: { + lon: 48.689445, + lat: 6.17651, + }, + }); + expect(addressVO.houseNumber).toBe('5'); + expect(addressVO.street).toBe('rue de la monnaie'); + expect(addressVO.locality).toBe('Nancy'); + expect(addressVO.postalCode).toBe('54000'); + expect(addressVO.country).toBe('France'); + expect(addressVO.coordinates.lon).toBe(48.689445); + expect(addressVO.name).toBeUndefined(); + }); +}); diff --git a/src/modules/ad/tests/unit/core/coordinates.value-object.spec.ts b/src/modules/ad/tests/unit/core/coordinates.value-object.spec.ts new file mode 100644 index 0000000..4718ac5 --- /dev/null +++ b/src/modules/ad/tests/unit/core/coordinates.value-object.spec.ts @@ -0,0 +1,12 @@ +import { Coordinates } from '@modules/ad/core/value-objects/coordinates.value-object'; + +describe('Coordinates value object', () => { + it('should create a coordinates value object', () => { + const coordinatesVO = new Coordinates({ + lon: 48.689445, + lat: 6.17651, + }); + expect(coordinatesVO.lon).toBe(48.689445); + expect(coordinatesVO.lat).toBe(6.17651); + }); +}); diff --git a/src/modules/ad/tests/unit/core/create-ad.service.spec.ts b/src/modules/ad/tests/unit/core/create-ad.service.spec.ts new file mode 100644 index 0000000..f2577f1 --- /dev/null +++ b/src/modules/ad/tests/unit/core/create-ad.service.spec.ts @@ -0,0 +1,131 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { CreateAdService } from '@modules/ad/core/commands/create-ad/create-ad.service'; +import { AD_REPOSITORY, PARAMS_PROVIDER } from '@modules/ad/ad.di-tokens'; +import { DefaultParamsProviderPort } from '@modules/ad/core/ports/default-params-provider.port'; +import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto'; +import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto'; +import { Frequency } from '@modules/ad/core/ad.types'; +import { CreateAdCommand } from '@modules/ad/core/commands/create-ad/create-ad.command'; +import { AggregateID } from '@libs/ddd'; +import { AdAlreadyExistsException } from '@modules/ad/core/ad.errors'; +import { AdEntity } from '@modules/ad/core/ad.entity'; +import { ConflictException } from '@libs/exceptions'; + +const originWaypoint: WaypointDto = { + position: 0, + lon: 48.689445, + lat: 6.17651, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', +}; +const destinationWaypoint: WaypointDto = { + position: 1, + lon: 48.8566, + lat: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', +}; +const punctualCreateAdRequest: CreateAdRequestDto = { + userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4', + fromDate: '2023-12-21', + toDate: '2023-12-21', + schedule: { + thu: '08:15', + }, + driver: true, + passenger: true, + seatsRequested: 1, + frequency: Frequency.PUNCTUAL, + waypoints: [originWaypoint, destinationWaypoint], +}; + +const mockAdRepository = { + insert: jest + .fn() + .mockImplementationOnce(() => ({})) + .mockImplementationOnce(() => { + throw new Error(); + }) + .mockImplementationOnce(() => { + throw new ConflictException('already exists'); + }), +}; + +const mockDefaultParamsProvider: DefaultParamsProviderPort = { + getParams: () => { + return { + MON_MARGIN: 900, + TUE_MARGIN: 900, + WED_MARGIN: 900, + THU_MARGIN: 900, + FRI_MARGIN: 900, + SAT_MARGIN: 900, + SUN_MARGIN: 900, + DRIVER: false, + SEATS_PROPOSED: 3, + PASSENGER: true, + SEATS_REQUESTED: 1, + STRICT: false, + DEFAULT_TIMEZONE: 'Europe/Paris', + }; + }, +}; + +describe('create-ad.service', () => { + let createAdService: CreateAdService; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: AD_REPOSITORY, + useValue: mockAdRepository, + }, + { + provide: PARAMS_PROVIDER, + useValue: mockDefaultParamsProvider, + }, + CreateAdService, + ], + }).compile(); + + createAdService = module.get(CreateAdService); + }); + + it('should be defined', () => { + expect(createAdService).toBeDefined(); + }); + + describe('execution', () => { + const createAdCommand = new CreateAdCommand(punctualCreateAdRequest); + it('should create a new ad', async () => { + AdEntity.create = jest.fn().mockReturnValue({ + id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', + }); + const result: AggregateID = await createAdService.execute( + createAdCommand, + ); + expect(result).toBe('047a6ecf-23d4-4d3e-877c-3225d560a8da'); + }); + it('should throw an error if something bad happens', async () => { + AdEntity.create = jest.fn().mockReturnValue({ + id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', + }); + await expect( + createAdService.execute(createAdCommand), + ).rejects.toBeInstanceOf(Error); + }); + it('should throw an exception if Ad already exists', async () => { + AdEntity.create = jest.fn().mockReturnValue({ + id: '047a6ecf-23d4-4d3e-877c-3225d560a8da', + }); + await expect( + createAdService.execute(createAdCommand), + ).rejects.toBeInstanceOf(AdAlreadyExistsException); + }); + }); +}); diff --git a/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts b/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts new file mode 100644 index 0000000..96bcabf --- /dev/null +++ b/src/modules/ad/tests/unit/core/find-ad-by-id.query-handler.spec.ts @@ -0,0 +1,132 @@ +import { AD_REPOSITORY } from '@modules/ad/ad.di-tokens'; +import { AdEntity } from '@modules/ad/core/ad.entity'; +import { + CreateAdProps, + DefaultAdProps, + Frequency, +} from '@modules/ad/core/ad.types'; +import { FindAdByIdQuery } from '@modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query'; +import { FindAdByIdQueryHandler } from '@modules/ad/core/queries/find-ad-by-id/find-ad-by-id.query-handler'; +import { MarginDurationsProps } from '@modules/ad/core/value-objects/margin-durations.value-object'; +import { WaypointProps } from '@modules/ad/core/value-objects/waypoint.value-object'; +import { Test, TestingModule } from '@nestjs/testing'; + +const originWaypointProps: WaypointProps = { + position: 0, + address: { + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + coordinates: { + lon: 48.689445, + lat: 6.17651, + }, + }, +}; +const destinationWaypointProps: WaypointProps = { + position: 1, + address: { + locality: 'Paris', + postalCode: '75000', + country: 'France', + coordinates: { + lon: 48.8566, + lat: 2.3522, + }, + }, +}; +const marginDurationsProps: MarginDurationsProps = { + mon: 600, + tue: 600, + wed: 600, + thu: 600, + fri: 600, + sat: 600, + sun: 600, +}; +const baseCreateAdProps = { + userId: 'e8fe64b1-4c33-49e1-9f69-4db48b21df36', + seatsProposed: 3, + seatsRequested: 1, + strict: false, + waypoints: [originWaypointProps, destinationWaypointProps], +}; +const punctualCreateAdProps = { + fromDate: '2023-06-22', + toDate: '2023-06-22', + schedule: { + wed: '08:30', + }, + frequency: Frequency.PUNCTUAL, +}; +const punctualPassengerCreateAdProps: CreateAdProps = { + ...baseCreateAdProps, + ...punctualCreateAdProps, + marginDurations: marginDurationsProps, + driver: false, + passenger: true, +}; + +const defaultAdProps: DefaultAdProps = { + driver: false, + passenger: true, + marginDurations: { + mon: 900, + tue: 900, + wed: 900, + thu: 900, + fri: 900, + sat: 900, + sun: 900, + }, + seatsProposed: 3, + seatsRequested: 1, + strict: false, +}; + +const ad: AdEntity = AdEntity.create( + punctualPassengerCreateAdProps, + defaultAdProps, +); + +const mockAdRepository = { + findOneById: jest.fn().mockImplementation(() => ad), +}; + +describe('find-ad-by-id.query-handler', () => { + let findAdByIdQueryHandler: FindAdByIdQueryHandler; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: AD_REPOSITORY, + useValue: mockAdRepository, + }, + FindAdByIdQueryHandler, + ], + }).compile(); + + findAdByIdQueryHandler = module.get( + FindAdByIdQueryHandler, + ); + }); + + it('should be defined', () => { + expect(findAdByIdQueryHandler).toBeDefined(); + }); + + describe('execution', () => { + it('should return an ad', async () => { + const findAdbyIdQuery = new FindAdByIdQuery( + 'dd264806-13b4-4226-9b18-87adf0ad5dd1', + ); + const ad: AdEntity = await findAdByIdQueryHandler.execute( + findAdbyIdQuery, + ); + expect(ad.getProps().fromDate).toBe('2023-06-22'); + }); + }); +}); diff --git a/src/modules/ad/tests/unit/core/margin-durations.value-object.spec.ts b/src/modules/ad/tests/unit/core/margin-durations.value-object.spec.ts new file mode 100644 index 0000000..c7e99d4 --- /dev/null +++ b/src/modules/ad/tests/unit/core/margin-durations.value-object.spec.ts @@ -0,0 +1,47 @@ +import { MarginDurations } from '@modules/ad/core/value-objects/margin-durations.value-object'; + +describe('Margin durations value object', () => { + it('should create a margin durations value object', () => { + const marginDurationsVO = new MarginDurations({ + mon: 600, + tue: 610, + wed: 620, + thu: 630, + fri: 640, + sat: 650, + sun: 660, + }); + expect(marginDurationsVO.mon).toBe(600); + expect(marginDurationsVO.tue).toBe(610); + expect(marginDurationsVO.wed).toBe(620); + expect(marginDurationsVO.thu).toBe(630); + expect(marginDurationsVO.fri).toBe(640); + expect(marginDurationsVO.sat).toBe(650); + expect(marginDurationsVO.sun).toBe(660); + }); + it('should update margin durations value object values', () => { + const marginDurationsVO = new MarginDurations({ + mon: 600, + tue: 610, + wed: 620, + thu: 630, + fri: 640, + sat: 650, + sun: 660, + }); + marginDurationsVO.mon = 700; + marginDurationsVO.tue = 710; + marginDurationsVO.wed = 720; + marginDurationsVO.thu = 730; + marginDurationsVO.fri = 740; + marginDurationsVO.sat = 750; + marginDurationsVO.sun = 760; + expect(marginDurationsVO.mon).toBe(700); + expect(marginDurationsVO.tue).toBe(710); + expect(marginDurationsVO.wed).toBe(720); + expect(marginDurationsVO.thu).toBe(730); + expect(marginDurationsVO.fri).toBe(740); + expect(marginDurationsVO.sat).toBe(750); + expect(marginDurationsVO.sun).toBe(760); + }); +}); diff --git a/src/modules/ad/tests/unit/core/publish-log-message-when-ad-is-created.domain-event-handler.spec.ts b/src/modules/ad/tests/unit/core/publish-log-message-when-ad-is-created.domain-event-handler.spec.ts new file mode 100644 index 0000000..2dc72fd --- /dev/null +++ b/src/modules/ad/tests/unit/core/publish-log-message-when-ad-is-created.domain-event-handler.spec.ts @@ -0,0 +1,94 @@ +import { Frequency } from '@modules/ad/core/ad.types'; +import { PublishLogMessageWhenAdIsCreatedDomainEventHandler } from '@modules/ad/core/event-handlers/publish-log-message-when-ad-is-created.domain-event-handler'; +import { AdCreatedDomainEvent } from '@modules/ad/core/events/ad-created.domain-events'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; + +const mockMessagePublisher = { + publish: jest.fn().mockImplementation(), +}; + +describe('Publish log message when ad is created domain event handler', () => { + let publishLogMessageWhenAdIsCreatedDomainEventHandler: PublishLogMessageWhenAdIsCreatedDomainEventHandler; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: MESSAGE_PUBLISHER, + useValue: mockMessagePublisher, + }, + PublishLogMessageWhenAdIsCreatedDomainEventHandler, + ], + }).compile(); + + publishLogMessageWhenAdIsCreatedDomainEventHandler = + module.get( + PublishLogMessageWhenAdIsCreatedDomainEventHandler, + ); + }); + + it('should publish a log message', () => { + jest.spyOn(mockMessagePublisher, 'publish'); + const adCreatedDomainEvent: AdCreatedDomainEvent = { + id: 'some-domain-event-id', + aggregateId: 'some-aggregate-id', + userId: 'some-user-id', + driver: false, + passenger: true, + frequency: Frequency.PUNCTUAL, + fromDate: '2023-06-28', + toDate: '2023-06-28', + monTime: undefined, + tueTime: undefined, + wedTime: '07:15', + thuTime: undefined, + friTime: undefined, + satTime: undefined, + sunTime: undefined, + monMarginDuration: 900, + tueMarginDuration: 900, + wedMarginDuration: 900, + thuMarginDuration: 900, + friMarginDuration: 900, + satMarginDuration: 900, + sunMarginDuration: 900, + seatsProposed: 3, + seatsRequested: 1, + strict: false, + waypoints: [ + { + position: 0, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + lat: 48.689445, + lon: 6.1765102, + }, + { + position: 1, + locality: 'Paris', + postalCode: '75000', + country: 'France', + lat: 48.8566, + lon: 2.3522, + }, + ], + metadata: { + timestamp: new Date('2023-06-28T05:00:00Z').getTime(), + correlationId: 'some-correlation-id', + }, + }; + publishLogMessageWhenAdIsCreatedDomainEventHandler.handle( + adCreatedDomainEvent, + ); + expect(publishLogMessageWhenAdIsCreatedDomainEventHandler).toBeDefined(); + expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1); + expect(mockMessagePublisher.publish).toHaveBeenCalledWith( + 'logging.ad.created.info', + '{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","userId":"some-user-id","driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-06-28","toDate":"2023-06-28","wedTime":"07:15","monMarginDuration":900,"tueMarginDuration":900,"wedMarginDuration":900,"thuMarginDuration":900,"friMarginDuration":900,"satMarginDuration":900,"sunMarginDuration":900,"seatsProposed":3,"seatsRequested":1,"strict":false,"waypoints":[{"position":0,"houseNumber":"5","street":"Avenue Foch","locality":"Nancy","postalCode":"54000","country":"France","lat":48.689445,"lon":6.1765102},{"position":1,"locality":"Paris","postalCode":"75000","country":"France","lat":48.8566,"lon":2.3522}],"metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}', + ); + }); +}); diff --git a/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts b/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts new file mode 100644 index 0000000..d07fc97 --- /dev/null +++ b/src/modules/ad/tests/unit/core/publish-message-when-ad-is-created.domain-event-handler.spec.ts @@ -0,0 +1,94 @@ +import { Frequency } from '@modules/ad/core/ad.types'; +import { PublishMessageWhenAdIsCreatedDomainEventHandler } from '@modules/ad/core/event-handlers/publish-message-when-ad-is-created.domain-event-handler'; +import { AdCreatedDomainEvent } from '@modules/ad/core/events/ad-created.domain-events'; +import { Test, TestingModule } from '@nestjs/testing'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; + +const mockMessagePublisher = { + publish: jest.fn().mockImplementation(), +}; + +describe('Publish message when ad is created domain event handler', () => { + let publishMessageWhenAdIsCreatedDomainEventHandler: PublishMessageWhenAdIsCreatedDomainEventHandler; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: MESSAGE_PUBLISHER, + useValue: mockMessagePublisher, + }, + PublishMessageWhenAdIsCreatedDomainEventHandler, + ], + }).compile(); + + publishMessageWhenAdIsCreatedDomainEventHandler = + module.get( + PublishMessageWhenAdIsCreatedDomainEventHandler, + ); + }); + + it('should publish a message', () => { + jest.spyOn(mockMessagePublisher, 'publish'); + const adCreatedDomainEvent: AdCreatedDomainEvent = { + id: 'some-domain-event-id', + aggregateId: 'some-aggregate-id', + userId: 'some-user-id', + driver: false, + passenger: true, + frequency: Frequency.PUNCTUAL, + fromDate: '2023-06-28', + toDate: '2023-06-28', + monTime: undefined, + tueTime: undefined, + wedTime: '07:15', + thuTime: undefined, + friTime: undefined, + satTime: undefined, + sunTime: undefined, + monMarginDuration: 900, + tueMarginDuration: 900, + wedMarginDuration: 900, + thuMarginDuration: 900, + friMarginDuration: 900, + satMarginDuration: 900, + sunMarginDuration: 900, + seatsProposed: 3, + seatsRequested: 1, + strict: false, + waypoints: [ + { + position: 0, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + lat: 48.689445, + lon: 6.1765102, + }, + { + position: 1, + locality: 'Paris', + postalCode: '75000', + country: 'France', + lat: 48.8566, + lon: 2.3522, + }, + ], + metadata: { + timestamp: new Date('2023-06-28T05:00:00Z').getTime(), + correlationId: 'some-correlation-id', + }, + }; + publishMessageWhenAdIsCreatedDomainEventHandler.handle( + adCreatedDomainEvent, + ); + expect(publishMessageWhenAdIsCreatedDomainEventHandler).toBeDefined(); + expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1); + expect(mockMessagePublisher.publish).toHaveBeenCalledWith( + 'ad.created', + '{"id":"some-domain-event-id","aggregateId":"some-aggregate-id","userId":"some-user-id","driver":false,"passenger":true,"frequency":"PUNCTUAL","fromDate":"2023-06-28","toDate":"2023-06-28","wedTime":"07:15","monMarginDuration":900,"tueMarginDuration":900,"wedMarginDuration":900,"thuMarginDuration":900,"friMarginDuration":900,"satMarginDuration":900,"sunMarginDuration":900,"seatsProposed":3,"seatsRequested":1,"strict":false,"waypoints":[{"position":0,"houseNumber":"5","street":"Avenue Foch","locality":"Nancy","postalCode":"54000","country":"France","lat":48.689445,"lon":6.1765102},{"position":1,"locality":"Paris","postalCode":"75000","country":"France","lat":48.8566,"lon":2.3522}],"metadata":{"timestamp":1687928400000,"correlationId":"some-correlation-id"}}', + ); + }); +}); diff --git a/src/modules/ad/tests/unit/core/schedule.value-object.spec.ts b/src/modules/ad/tests/unit/core/schedule.value-object.spec.ts new file mode 100644 index 0000000..b4d14bd --- /dev/null +++ b/src/modules/ad/tests/unit/core/schedule.value-object.spec.ts @@ -0,0 +1,22 @@ +import { Schedule } from '@modules/ad/core/value-objects/schedule.value-object'; + +describe('Schedule value object', () => { + it('should create a schedule value object', () => { + const scheduleVO = new Schedule({ + mon: '07:00', + tue: '07:05', + wed: '07:10', + thu: '07:15', + fri: '07:20', + sat: '07:25', + sun: '07:30', + }); + expect(scheduleVO.mon).toBe('07:00'); + expect(scheduleVO.tue).toBe('07:05'); + expect(scheduleVO.wed).toBe('07:10'); + expect(scheduleVO.thu).toBe('07:15'); + expect(scheduleVO.fri).toBe('07:20'); + expect(scheduleVO.sat).toBe('07:25'); + expect(scheduleVO.sun).toBe('07:30'); + }); +}); diff --git a/src/modules/ad/tests/unit/core/waypoint.value-object.spec.ts b/src/modules/ad/tests/unit/core/waypoint.value-object.spec.ts new file mode 100644 index 0000000..ed4b842 --- /dev/null +++ b/src/modules/ad/tests/unit/core/waypoint.value-object.spec.ts @@ -0,0 +1,22 @@ +import { Waypoint } from '@modules/ad/core/value-objects/waypoint.value-object'; + +describe('Waypoint value object', () => { + it('should create a waypoint value object', () => { + const waypointVO = new Waypoint({ + position: 0, + address: { + houseNumber: '5', + street: 'rue de la monnaie', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + coordinates: { + lon: 48.689445, + lat: 6.17651, + }, + }, + }); + expect(waypointVO.position).toBe(0); + expect(waypointVO.address.country).toBe('France'); + }); +}); diff --git a/src/modules/ad/tests/unit/domain/create-ad.usecase.spec.ts b/src/modules/ad/tests/unit/domain/create-ad.usecase.spec.ts deleted file mode 100644 index 8ef3770..0000000 --- a/src/modules/ad/tests/unit/domain/create-ad.usecase.spec.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { CreateAdUseCase } from '../../../domain/usecases/create-ad.usecase'; -import { CreateAdRequest } from '../../../domain/dtos/create-ad.request'; -import { AdsRepository } from '../../../adapters/secondaries/ads.repository'; -import { CreateAdCommand } from '../../../commands/create-ad.command'; -import { AutomapperModule } from '@automapper/nestjs'; -import { classes } from '@automapper/classes'; -import { Frequency } from '../../../domain/types/frequency.enum'; -import { Ad } from '../../../domain/entities/ad'; -import { AdProfile } from '../../../mappers/ad.profile'; -import { AddressDTO } from '../../../domain/dtos/address.dto'; -import { AdCreation } from '../../../domain/dtos/ad.creation'; -import { Address } from '../../../domain/entities/address'; -import { PARAMS_PROVIDER } from '../../../ad.constants'; -import { MESSAGE_PUBLISHER } from '../../../../../app.constants'; - -const mockAddress1: AddressDTO = { - position: 0, - lon: 48.68944505415954, - lat: 6.176510296462267, - houseNumber: '5', - street: 'Avenue Foch', - locality: 'Nancy', - postalCode: '54000', - country: 'France', -}; -const mockAddress2: AddressDTO = { - position: 1, - lon: 48.8566, - lat: 2.3522, - locality: 'Paris', - postalCode: '75000', - country: 'France', -}; -const mockAddressWithoutPos1: AddressDTO = { - lon: 43.2965, - lat: 5.3698, - locality: 'Marseille', - postalCode: '13000', - country: 'France', -}; -const mockAddressWithoutPos2: AddressDTO = { - lon: 43.7102, - lat: 7.262, - locality: 'Nice', - postalCode: '06000', - country: 'France', -}; -const minimalRecurrentAdREquest: CreateAdRequest = { - userUuid: '224e0000-0000-4000-a000-000000000000', - frequency: Frequency.RECURRENT, - fromDate: new Date('01-05-2023'), - toDate: new Date('01-05-2024'), - schedule: { - mon: '08:00', - }, - marginDurations: {}, - addresses: [mockAddress1, mockAddress2], -}; -const newAdRequest: CreateAdRequest = { - userUuid: '113e0000-0000-4000-a000-000000000000', - driver: true, - passenger: false, - frequency: Frequency.RECURRENT, - fromDate: new Date('01-05-2023'), - toDate: new Date('20-08-2023'), - schedule: { - tue: '08:00', - wed: '08:30', - }, - marginDurations: { - mon: undefined, - tue: undefined, - wed: undefined, - thu: undefined, - fri: undefined, - sat: undefined, - sun: undefined, - }, - seatsDriver: 2, - addresses: [mockAddress1, mockAddress2], -}; - -const mockMessagePublisher = { - publish: jest.fn().mockImplementation(), -}; -const mockDefaultParamsProvider = { - getParams: () => { - return { - MON_MARGIN: 900, - TUE_MARGIN: 900, - WED_MARGIN: 900, - THU_MARGIN: 900, - FRI_MARGIN: 900, - SAT_MARGIN: 900, - SUN_MARGIN: 900, - DRIVER: false, - SEATS_PROVIDED: 0, - PASSENGER: true, - SEATS_REQUESTED: 1, - STRICT: false, - }; - }, -}; -const mockAdRepository = { - create: jest - .fn() - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((command?: CreateAdCommand) => { - return Promise.resolve({ - ...newAdRequest, - uuid: 'ad000000-0000-4000-a000-000000000000', - createdAt: new Date('01-05-2023'), - }); - }) - .mockImplementationOnce(() => { - throw new Error('Already exists'); - }) - .mockImplementation(), -}; -describe('CreateAdUseCase', () => { - let createAdUseCase: CreateAdUseCase; - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [AutomapperModule.forRoot({ strategyInitializer: classes() })], - providers: [ - { - provide: AdsRepository, - useValue: mockAdRepository, - }, - { - provide: MESSAGE_PUBLISHER, - useValue: mockMessagePublisher, - }, - CreateAdUseCase, - AdProfile, - { - provide: PARAMS_PROVIDER, - useValue: mockDefaultParamsProvider, - }, - ], - }).compile(); - - createAdUseCase = module.get(CreateAdUseCase); - }); - it('should be defined', () => { - expect(createAdUseCase).toBeDefined(); - }); - describe('execution', () => { - const newAdCommand = new CreateAdCommand(newAdRequest); - it('should create an new ad', async () => { - const newAd: Ad = await createAdUseCase.execute(newAdCommand); - expect(newAd.userUuid).toBe(newAdRequest.userUuid); - expect(newAd.uuid).toBeDefined(); - }); - it('should throw an error if the ad already exists', async () => { - await expect( - createAdUseCase.execute(newAdCommand), - ).rejects.toBeInstanceOf(Error); - }); - }); - - describe('Ad parameter default setting ', () => { - beforeEach(() => { - mockAdRepository.create.mockClear(); - }); - - it('should define mimimal ad as 1 passager add', async () => { - const newAdCommand = new CreateAdCommand(minimalRecurrentAdREquest); - await createAdUseCase.execute(newAdCommand); - const expectedAdCreation = { - userUuid: minimalRecurrentAdREquest.userUuid, - frequency: minimalRecurrentAdREquest.frequency, - fromDate: minimalRecurrentAdREquest.fromDate, - toDate: minimalRecurrentAdREquest.toDate, - monTime: minimalRecurrentAdREquest.schedule.mon, - tueTime: undefined, - wedTime: undefined, - thuTime: undefined, - friTime: undefined, - satTime: undefined, - sunTime: undefined, - monMargin: mockDefaultParamsProvider.getParams().MON_MARGIN, - tueMargin: mockDefaultParamsProvider.getParams().TUE_MARGIN, - wedMargin: mockDefaultParamsProvider.getParams().WED_MARGIN, - thuMargin: mockDefaultParamsProvider.getParams().THU_MARGIN, - friMargin: mockDefaultParamsProvider.getParams().FRI_MARGIN, - satMargin: mockDefaultParamsProvider.getParams().SAT_MARGIN, - sunMargin: mockDefaultParamsProvider.getParams().SUN_MARGIN, - driver: mockDefaultParamsProvider.getParams().DRIVER, - seatsDriver: mockDefaultParamsProvider.getParams().SEATS_PROVIDED, - passenger: mockDefaultParamsProvider.getParams().PASSENGER, - seatsPassenger: mockDefaultParamsProvider.getParams().SEATS_REQUESTED, - strict: mockDefaultParamsProvider.getParams().STRICT, - addresses: { - create: minimalRecurrentAdREquest.addresses as Address[], - }, - createdAt: undefined, - } as AdCreation; - - expect(mockAdRepository.create).toBeCalledWith(expectedAdCreation); - }); - it('should create an passengerAd with addresses without position ', async () => { - const newPunctualPassengerAdRequest: CreateAdRequest = { - userUuid: '113e0000-0000-4000-a000-000000000000', - passenger: true, - frequency: Frequency.PUNCTUAL, - departureDateTime: new Date('05-22-2023 09:36'), - - marginDurations: { - mon: undefined, - tue: undefined, - wed: undefined, - thu: undefined, - fri: undefined, - sat: undefined, - sun: undefined, - }, - seatsPassenger: 1, - addresses: [mockAddressWithoutPos1, mockAddressWithoutPos2], - schedule: {}, - }; - const newAdCommand = new CreateAdCommand(newPunctualPassengerAdRequest); - await createAdUseCase.execute(newAdCommand); - const expectedAdCreation = { - userUuid: newPunctualPassengerAdRequest.userUuid, - frequency: newPunctualPassengerAdRequest.frequency, - fromDate: newPunctualPassengerAdRequest.departureDateTime, - toDate: newPunctualPassengerAdRequest.departureDateTime, - monTime: '09:36', - tueTime: undefined, - wedTime: undefined, - thuTime: undefined, - friTime: undefined, - satTime: undefined, - sunTime: undefined, - monMargin: mockDefaultParamsProvider.getParams().MON_MARGIN, - tueMargin: mockDefaultParamsProvider.getParams().TUE_MARGIN, - wedMargin: mockDefaultParamsProvider.getParams().WED_MARGIN, - thuMargin: mockDefaultParamsProvider.getParams().THU_MARGIN, - friMargin: mockDefaultParamsProvider.getParams().FRI_MARGIN, - satMargin: mockDefaultParamsProvider.getParams().SAT_MARGIN, - sunMargin: mockDefaultParamsProvider.getParams().SUN_MARGIN, - driver: false, - seatsDriver: 0, - passenger: newPunctualPassengerAdRequest.passenger, - seatsPassenger: newPunctualPassengerAdRequest.seatsPassenger, - strict: mockDefaultParamsProvider.getParams().STRICT, - addresses: { - create: newPunctualPassengerAdRequest.addresses as Address[], - }, - createdAt: undefined, - } as AdCreation; - expect(mockAdRepository.create).toBeCalledWith(expectedAdCreation); - }); - }); -}); diff --git a/src/modules/ad/tests/unit/domain/find-ad-by-uuid.usecase.spec.ts b/src/modules/ad/tests/unit/domain/find-ad-by-uuid.usecase.spec.ts deleted file mode 100644 index d15ab75..0000000 --- a/src/modules/ad/tests/unit/domain/find-ad-by-uuid.usecase.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { NotFoundException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { FindAdByUuidQuery } from '../../../queries/find-ad-by-uuid.query'; -import { AdsRepository } from '../../../adapters/secondaries/ads.repository'; -import { FindAdByUuidUseCase } from '../../../domain/usecases/find-ad-by-uuid.usecase'; -import { FindAdByUuidRequest } from '../../../domain/dtos/find-ad-by-uuid.request'; -import { MESSAGE_PUBLISHER } from '../../../../../app.constants'; - -const mockAd = { - uuid: 'bb281075-1b98-4456-89d6-c643d3044a91', -}; - -const mockAdRepository = { - findOneByUuid: jest - .fn() - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((query?: FindAdByUuidQuery) => { - return Promise.resolve(mockAd); - }) - .mockImplementation(() => { - return Promise.resolve(null); - }), -}; - -const mockMessagePublisher = { - publish: jest.fn().mockImplementation(), -}; - -describe('FindAdByUuidUseCase', () => { - let findAdByUuidUseCase: FindAdByUuidUseCase; - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - imports: [], - providers: [ - { - provide: AdsRepository, - useValue: mockAdRepository, - }, - { - provide: MESSAGE_PUBLISHER, - useValue: mockMessagePublisher, - }, - FindAdByUuidUseCase, - ], - }).compile(); - - findAdByUuidUseCase = module.get(FindAdByUuidUseCase); - }); - it('should be defined', () => { - expect(findAdByUuidUseCase).toBeDefined(); - }); - describe('execute', () => { - it('should return an ad', async () => { - const findAdByUuidRequest: FindAdByUuidRequest = - new FindAdByUuidRequest(); - findAdByUuidRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a91'; - const ad = await findAdByUuidUseCase.execute( - new FindAdByUuidQuery(findAdByUuidRequest), - ); - expect(ad).toBe(mockAd); - }); - it('should throw an error if ad does not exist', async () => { - const findAdByUuidRequest: FindAdByUuidRequest = - new FindAdByUuidRequest(); - findAdByUuidRequest.uuid = 'bb281075-1b98-4456-89d6-c643d3044a90'; - await expect( - findAdByUuidUseCase.execute(new FindAdByUuidQuery(findAdByUuidRequest)), - ).rejects.toBeInstanceOf(NotFoundException); - }); - }); -}); diff --git a/src/modules/ad/tests/unit/domain/frequency.mapping.spec.ts b/src/modules/ad/tests/unit/domain/frequency.mapping.spec.ts deleted file mode 100644 index 6a08276..0000000 --- a/src/modules/ad/tests/unit/domain/frequency.mapping.spec.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { intToFrequency } from '../../../domain/dtos/validators/frequency.mapping'; -import { Frequency } from '../../../domain/types/frequency.enum'; - -describe('frequency mapping function ', () => { - it('should return punctual', () => { - expect(intToFrequency(1)).toBe(Frequency.PUNCTUAL); - }); - it('should return recurrent', () => { - expect(intToFrequency(2)).toBe(Frequency.RECURRENT); - }); - it('should return undefined', () => { - expect(intToFrequency(0)).toBeUndefined(); - expect(intToFrequency(3)).toBeUndefined(); - }); -}); diff --git a/src/modules/ad/tests/unit/domain/has-driver-seats-validator.spec.ts b/src/modules/ad/tests/unit/domain/has-driver-seats-validator.spec.ts deleted file mode 100644 index 35bad11..0000000 --- a/src/modules/ad/tests/unit/domain/has-driver-seats-validator.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { hasProperDriverSeats } from '../../../domain/dtos/validators/has-driver-seats'; - -describe('driver and/or driver seats validator', () => { - it('should validate if driver and drivers seats is not provided ', () => { - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: undefined }, - property: '', - }), - ).toBe(true); - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: false }, - property: '', - }), - ).toBe(true); - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: null }, - property: '', - }), - ).toBe(true); - }); - it('should not validate if driver is set to true but not the related seats is not provided or 0', () => { - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: true }, - property: '', - }), - ).toBe(false); - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: true, seatsDriver: 0 }, - property: '', - }), - ).toBe(false); - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: true, seatsDriver: undefined }, - property: '', - }), - ).toBe(false); - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: true, seatsDriver: null }, - property: '', - }), - ).toBe(false); - }); - it('should not validate if driver seats are provided but driver is not set or set to false ', () => { - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: false, seatsDriver: 1 }, - property: '', - }), - ).toBe(false); - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: undefined, seatsDriver: 1 }, - property: '', - }), - ).toBe(false); - expect( - hasProperDriverSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { driver: null, seatsDriver: 1 }, - property: '', - }), - ).toBe(false); - }); -}); diff --git a/src/modules/ad/tests/unit/domain/has-passenger-seats-validator.spec.ts b/src/modules/ad/tests/unit/domain/has-passenger-seats-validator.spec.ts deleted file mode 100644 index ae5459e..0000000 --- a/src/modules/ad/tests/unit/domain/has-passenger-seats-validator.spec.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { hasProperPassengerSeats } from '../../../domain/dtos/validators/has-passenger-seats'; - -describe('driver and/or passenger seats validator', () => { - it('should validate if passenger and passengers seats is not provided ', () => { - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: undefined }, - property: '', - }), - ).toBe(true); - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: false }, - property: '', - }), - ).toBe(true); - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: null }, - property: '', - }), - ).toBe(true); - }); - it('should not validate if passenger is set to true but not the related seats is not provided or 0', () => { - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: true }, - property: '', - }), - ).toBe(false); - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: true, seatsPassenger: 0 }, - property: '', - }), - ).toBe(false); - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: true, seatsPassenger: undefined }, - property: '', - }), - ).toBe(false); - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: true, seatsPassenger: null }, - property: '', - }), - ).toBe(false); - }); - it('should not validate if passenger seats are provided but passenger is not set or set to false ', () => { - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: false, seatsPassenger: 1 }, - property: '', - }), - ).toBe(false); - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: undefined, seatsPassenger: 1 }, - property: '', - }), - ).toBe(false); - expect( - hasProperPassengerSeats({ - value: undefined, - constraints: [], - targetName: '', - object: { passenger: null, seatsPassenger: 1 }, - property: '', - }), - ).toBe(false); - }); -}); diff --git a/src/modules/ad/tests/unit/domain/has-proper-addresses-indexes.spec.ts b/src/modules/ad/tests/unit/domain/has-proper-addresses-indexes.spec.ts deleted file mode 100644 index 4a84a5e..0000000 --- a/src/modules/ad/tests/unit/domain/has-proper-addresses-indexes.spec.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { AddressDTO } from '../../../domain/dtos/address.dto'; -import { hasProperPositionIndexes } from '../../../domain/dtos/validators/address-position'; -describe('addresses position validators', () => { - const mockAddress1: AddressDTO = { - lon: 48.68944505415954, - lat: 6.176510296462267, - houseNumber: '5', - street: 'Avenue Foch', - locality: 'Nancy', - postalCode: '54000', - country: 'France', - }; - const mockAddress2: AddressDTO = { - lon: 48.8566, - lat: 2.3522, - locality: 'Paris', - postalCode: '75000', - country: 'France', - }; - - const mockAddress3: AddressDTO = { - lon: 49.2628, - lat: 4.0347, - locality: 'Reims', - postalCode: '51454', - country: 'France', - }; - it('should validate if none of position is definded ', () => { - expect( - hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), - ).toBeTruthy(); - }); - it('should throw an error if position are partialy defined ', () => { - mockAddress1.position = 0; - expect( - hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), - ).toBeFalsy(); - }); - it('should throw an error if position are partialy defined ', () => { - mockAddress1.position = 0; - mockAddress2.position = null; - mockAddress3.position = undefined; - expect( - hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), - ).toBeFalsy(); - }); - - it('should throw an error if positions are not incremented ', () => { - mockAddress1.position = 0; - mockAddress2.position = 1; - mockAddress3.position = 1; - expect( - hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), - ).toBeFalsy(); - }); - it('should validate if all positions are defined and incremented', () => { - mockAddress1.position = 0; - mockAddress2.position = 1; - mockAddress3.position = 2; - expect( - hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), - ).toBeTruthy(); - mockAddress1.position = 10; - mockAddress2.position = 0; - mockAddress3.position = 3; - expect( - hasProperPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), - ).toBeTruthy(); - }); -}); diff --git a/src/modules/ad/tests/unit/domain/is-punctual-or-reccurent.spec.ts b/src/modules/ad/tests/unit/domain/is-punctual-or-reccurent.spec.ts deleted file mode 100644 index 89438cc..0000000 --- a/src/modules/ad/tests/unit/domain/is-punctual-or-reccurent.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { isPunctualOrRecurrent } from '../../../domain/dtos/validators/is-punctual-or-recurrent'; -import { Frequency } from '../../../domain/types/frequency.enum'; - -describe('punctual or recurrent validators', () => { - describe('punctual case ', () => { - describe('valid cases', () => { - it('should validate with valid departure DateTime and empty schedule ', () => { - expect( - isPunctualOrRecurrent({ - value: undefined, - constraints: [], - targetName: '', - object: { - frequency: Frequency.PUNCTUAL, - departureDateTime: new Date('01-02-2023'), - schedule: {}, - }, - property: '', - }), - ).toBeTruthy(); - }); - }); - describe('invalid cases ', () => { - it('should not validate with invalid departureDateTime and empty schedule and margin', () => { - expect( - isPunctualOrRecurrent({ - value: undefined, - constraints: [], - targetName: '', - object: { - frequency: Frequency.PUNCTUAL, - fromDate: new Date('20-10-2023'), - toDate: new Date('30-10-2023'), - }, - property: '', - }), - ).toBeFalsy(); - }); - it('should not validate with no empty schedule', () => { - expect( - isPunctualOrRecurrent({ - value: undefined, - constraints: [], - targetName: '', - object: { - frequency: Frequency.PUNCTUAL, - departureDateTime: new Date('01-02-2023'), - schedule: { - mon: '08:30', - }, - }, - property: '', - }), - ).toBeFalsy(); - }); - }); - }); - describe('recurrent case ', () => { - describe('valid cases', () => { - it('should validate with valid from date, to date and non empty schedule ', () => { - expect( - isPunctualOrRecurrent({ - value: undefined, - constraints: [], - targetName: '', - object: { - frequency: Frequency.RECURRENT, - fromDate: new Date('01-15-2023'), - toDate: new Date('06-30-2023'), - schedule: { - mon: '08:30', - }, - }, - property: '', - }), - ).toBeTruthy(); - }); - }); - describe('invalid cases ', () => { - it('should not validate with empty schedule ', () => { - expect( - isPunctualOrRecurrent({ - value: undefined, - constraints: [], - targetName: '', - object: { - frequency: Frequency.RECURRENT, - fromDate: new Date('01-15-2023'), - toDate: new Date('06-30-2023'), - schedule: {}, - }, - property: '', - }), - ).toBeFalsy(); - }); - it('should not validate with invalid from date to date and empty schedule and margin', () => { - expect( - isPunctualOrRecurrent({ - value: undefined, - constraints: [], - targetName: '', - object: { - frequency: Frequency.RECURRENT, - departureDateTime: new Date('20-10-2023'), - toDate: new Date('30-10-2023'), - }, - property: '', - }), - ).toBeFalsy(); - }); - }); - }); -}); diff --git a/src/modules/ad/tests/unit/domain/recurrent-normaliser.spec.ts b/src/modules/ad/tests/unit/domain/recurrent-normaliser.spec.ts deleted file mode 100644 index f94e52e..0000000 --- a/src/modules/ad/tests/unit/domain/recurrent-normaliser.spec.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { Day } from '../../../domain/types/day.enum'; -import { CreateAdRequest } from '../../../domain/dtos/create-ad.request'; -import { ScheduleDTO } from '../../../domain/dtos/schedule.dto'; -import { FrequencyNormaliser } from '../../../domain/entities/frequency.normaliser'; -import { Frequency } from '../../../domain/types/frequency.enum'; - -describe('recurrent normalizer transformer for punctual ad ', () => { - const recurrentNormaliser = new FrequencyNormaliser(); - it('should transform punctual ad into recurrent ad ', () => { - const punctualAd: CreateAdRequest = { - userUuid: '', - frequency: Frequency.PUNCTUAL, - departureDateTime: new Date('05-03-2023 12:39:39 '), - schedule: {} as ScheduleDTO, - addresses: [], - }; - expect(recurrentNormaliser.fromDateResolver(punctualAd)).toBe( - punctualAd.departureDateTime, - ); - expect(recurrentNormaliser.toDateResolver(punctualAd)).toBe( - punctualAd.departureDateTime, - ); - expect( - recurrentNormaliser.scheduleResolver(punctualAd, Day.mon), - ).toBeUndefined(); - expect( - recurrentNormaliser.scheduleResolver(punctualAd, Day.tue), - ).toBeUndefined(); - expect(recurrentNormaliser.scheduleResolver(punctualAd, Day.wed)).toBe( - '12:39', - ); - expect( - recurrentNormaliser.scheduleResolver(punctualAd, Day.thu), - ).toBeUndefined(); - expect( - recurrentNormaliser.scheduleResolver(punctualAd, Day.fri), - ).toBeUndefined(); - expect( - recurrentNormaliser.scheduleResolver(punctualAd, Day.sat), - ).toBeUndefined(); - expect( - recurrentNormaliser.scheduleResolver(punctualAd, Day.sun), - ).toBeUndefined(); - }); - it('should leave recurrent ad as is', () => { - const recurrentAd: CreateAdRequest = { - userUuid: '', - frequency: Frequency.RECURRENT, - schedule: { - mon: '08:30', - tue: '08:30', - wed: '09:00', - fri: '09:00', - }, - addresses: [], - }; - expect(recurrentNormaliser.fromDateResolver(recurrentAd)).toBe( - recurrentAd.departureDateTime, - ); - expect(recurrentNormaliser.toDateResolver(recurrentAd)).toBe( - recurrentAd.departureDateTime, - ); - expect(recurrentNormaliser.scheduleResolver(recurrentAd, Day.mon)).toBe( - recurrentAd.schedule.mon, - ); - expect(recurrentNormaliser.scheduleResolver(recurrentAd, Day.tue)).toBe( - recurrentAd.schedule.tue, - ); - expect(recurrentNormaliser.scheduleResolver(recurrentAd, Day.wed)).toBe( - recurrentAd.schedule.wed, - ); - expect(recurrentNormaliser.scheduleResolver(recurrentAd, Day.thu)).toBe( - recurrentAd.schedule.thu, - ); - expect(recurrentNormaliser.scheduleResolver(recurrentAd, Day.fri)).toBe( - recurrentAd.schedule.fri, - ); - expect(recurrentNormaliser.scheduleResolver(recurrentAd, Day.sat)).toBe( - recurrentAd.schedule.sat, - ); - expect(recurrentNormaliser.scheduleResolver(recurrentAd, Day.sun)).toBe( - recurrentAd.schedule.sun, - ); - }); - it('should pass for each day of the week of a deprarture ', () => { - const punctualAd: CreateAdRequest = { - userUuid: '', - frequency: Frequency.PUNCTUAL, - departureDateTime: undefined, - schedule: {} as ScheduleDTO, - addresses: [], - }; - punctualAd.departureDateTime = new Date('05-01-2023 '); - expect(recurrentNormaliser.scheduleResolver(punctualAd, Day.mon)).toBe( - '00:00', - ); - punctualAd.departureDateTime = new Date('05-02-2023 06:32:45'); - expect(recurrentNormaliser.scheduleResolver(punctualAd, Day.tue)).toBe( - '06:32', - ); - punctualAd.departureDateTime = new Date('05-03-2023 10:21'); - expect(recurrentNormaliser.scheduleResolver(punctualAd, Day.wed)).toBe( - '10:21', - ); - punctualAd.departureDateTime = new Date('05-04-2023 11:06:00'); - expect(recurrentNormaliser.scheduleResolver(punctualAd, Day.thu)).toBe( - '11:06', - ); - punctualAd.departureDateTime = new Date('05-05-2023 05:20'); - expect(recurrentNormaliser.scheduleResolver(punctualAd, Day.fri)).toBe( - '05:20', - ); - punctualAd.departureDateTime = new Date('05-06-2023'); - expect(recurrentNormaliser.scheduleResolver(punctualAd, Day.sat)).toBe( - '00:00', - ); - punctualAd.departureDateTime = new Date('05-07-2023'); - expect(recurrentNormaliser.scheduleResolver(punctualAd, Day.sun)).toBe( - '00:00', - ); - }); -}); diff --git a/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts b/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts new file mode 100644 index 0000000..88b9e48 --- /dev/null +++ b/src/modules/ad/tests/unit/infrastructure/ad.repository.spec.ts @@ -0,0 +1,88 @@ +import { PrismaService } from '@libs/db/prisma.service'; +import { + PARAMS_PROVIDER, + TIMEZONE_FINDER, + TIME_CONVERTER, +} from '@modules/ad/ad.di-tokens'; +import { AdMapper } from '@modules/ad/ad.mapper'; +import { DefaultParamsProviderPort } from '@modules/ad/core/ports/default-params-provider.port'; +import { TimeConverterPort } from '@modules/ad/core/ports/time-converter.port'; +import { TimezoneFinderPort } from '@modules/ad/core/ports/timezone-finder.port'; +import { AdRepository } from '@modules/ad/infrastructure/ad.repository'; +import { EventEmitter2, EventEmitterModule } from '@nestjs/event-emitter'; +import { Test, TestingModule } from '@nestjs/testing'; + +const mockDefaultParamsProvider: DefaultParamsProviderPort = { + getParams: () => { + return { + MON_MARGIN: 900, + TUE_MARGIN: 900, + WED_MARGIN: 900, + THU_MARGIN: 900, + FRI_MARGIN: 900, + SAT_MARGIN: 900, + SUN_MARGIN: 900, + DRIVER: false, + SEATS_PROPOSED: 3, + PASSENGER: true, + SEATS_REQUESTED: 1, + STRICT: false, + DEFAULT_TIMEZONE: 'Europe/Paris', + }; + }, +}; + +const mockTimezoneFinder: TimezoneFinderPort = { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + timezones: jest.fn().mockImplementation((lon: number, lat: number) => { + if (lon < 60) return 'Europe/Paris'; + return 'America/New_York'; + }), +}; + +const mockTimeConverter: TimeConverterPort = { + localDateTimeToUtc: jest + .fn() + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .mockImplementation((datetime: Date, timezone: string, dst?: boolean) => { + return datetime; + }), + utcDatetimeToLocalTime: jest.fn(), +}; + +describe('Ad repository', () => { + let prismaService: PrismaService; + let adMapper: AdMapper; + let eventEmitter: EventEmitter2; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [EventEmitterModule.forRoot()], + providers: [ + PrismaService, + AdMapper, + { + provide: PARAMS_PROVIDER, + useValue: mockDefaultParamsProvider, + }, + { + provide: TIMEZONE_FINDER, + useValue: mockTimezoneFinder, + }, + { + provide: TIME_CONVERTER, + useValue: mockTimeConverter, + }, + ], + }).compile(); + + prismaService = module.get(PrismaService); + adMapper = module.get(AdMapper); + eventEmitter = module.get(EventEmitter2); + }); + it('should be defined', () => { + expect( + new AdRepository(prismaService, adMapper, eventEmitter), + ).toBeDefined(); + }); +}); diff --git a/src/modules/ad/tests/unit/adapters/secondaries/default-param.provider.spec.ts b/src/modules/ad/tests/unit/infrastructure/default-param.provider.spec.ts similarity index 51% rename from src/modules/ad/tests/unit/adapters/secondaries/default-param.provider.spec.ts rename to src/modules/ad/tests/unit/infrastructure/default-param.provider.spec.ts index 3096b7e..57ad3d8 100644 --- a/src/modules/ad/tests/unit/adapters/secondaries/default-param.provider.spec.ts +++ b/src/modules/ad/tests/unit/infrastructure/default-param.provider.spec.ts @@ -1,12 +1,29 @@ +import { DefaultParams } from '@modules/ad/core/ports/default-params.type'; +import { DefaultParamsProvider } from '@modules/ad/infrastructure/default-params-provider'; import { ConfigService } from '@nestjs/config'; import { Test, TestingModule } from '@nestjs/testing'; -import { DefaultParamsProvider } from '../../../../adapters/secondaries/default-params.provider'; -import { DefaultParams } from '../../../../domain/types/default-params.type'; const mockConfigService = { - get: jest.fn().mockImplementation(() => 'some_default_value'), + get: jest.fn().mockImplementation((value: string) => { + switch (value) { + case 'DEPARTURE_MARGIN': + return 900; + case 'ROLE': + return 'passenger'; + case 'SEATS_PROPOSED': + return 3; + case 'SEATS_REQUESTED': + return 1; + case 'STRICT_FREQUENCY': + return 'false'; + case 'DEFAULT_TIMEZONE': + return 'Europe/Paris'; + default: + return 'some_default_value'; + } + }), }; -//TODO complete coverage + describe('DefaultParamsProvider', () => { let defaultParamsProvider: DefaultParamsProvider; @@ -33,8 +50,9 @@ describe('DefaultParamsProvider', () => { it('should provide default params', async () => { const params: DefaultParams = defaultParamsProvider.getParams(); - expect(params.SUN_MARGIN).toBeNaN(); - expect(params.PASSENGER).toBe(false); - expect(params.DRIVER).toBe(false); + expect(params.SUN_MARGIN).toBe(900); + expect(params.PASSENGER).toBeTruthy(); + expect(params.DRIVER).toBeFalsy(); + expect(params.DEFAULT_TIMEZONE).toBe('Europe/Paris'); }); }); diff --git a/src/modules/ad/tests/unit/adapters/secondaries/message-publisher.spec.ts b/src/modules/ad/tests/unit/infrastructure/message-publisher.spec.ts similarity index 84% rename from src/modules/ad/tests/unit/adapters/secondaries/message-publisher.spec.ts rename to src/modules/ad/tests/unit/infrastructure/message-publisher.spec.ts index d32a536..54dab1f 100644 --- a/src/modules/ad/tests/unit/adapters/secondaries/message-publisher.spec.ts +++ b/src/modules/ad/tests/unit/infrastructure/message-publisher.spec.ts @@ -1,6 +1,6 @@ +import { MessagePublisher } from '@modules/ad/infrastructure/message-publisher'; import { Test, TestingModule } from '@nestjs/testing'; -import { MessagePublisher } from '../../../../adapters/secondaries/message-publisher'; -import { MESSAGE_BROKER_PUBLISHER } from '../../../../../../app.constants'; +import { MESSAGE_BROKER_PUBLISHER } from '@src/app.constants'; const mockMessageBrokerPublisher = { publish: jest.fn().mockImplementation(), diff --git a/src/modules/ad/tests/unit/infrastructure/time-converter.spec.ts b/src/modules/ad/tests/unit/infrastructure/time-converter.spec.ts new file mode 100644 index 0000000..f5634e0 --- /dev/null +++ b/src/modules/ad/tests/unit/infrastructure/time-converter.spec.ts @@ -0,0 +1,96 @@ +import { TimeConverter } from '@modules/ad/infrastructure/time-converter'; + +describe('Time Converter', () => { + it('should be defined', () => { + const timeConverter: TimeConverter = new TimeConverter(); + expect(timeConverter).toBeDefined(); + }); + + describe('localDateTimeToUtc', () => { + it('should convert a paris datetime to utc', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const parisDate = '2023-06-22'; + const parisTime = '08:00'; + const utcDatetime = timeConverter.localDateTimeToUtc( + parisDate, + parisTime, + 'Europe/Paris', + ); + expect(utcDatetime.toISOString()).toEqual('2023-06-22T06:00:00.000Z'); + }); + it('should return undefined if date is invalid', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const parisDate = '2023-16-22'; + const parisTime = '08:00'; + const utcDatetime = timeConverter.localDateTimeToUtc( + parisDate, + parisTime, + 'Europe/Paris', + ); + expect(utcDatetime).toBeUndefined(); + }); + it('should return undefined if time is invalid', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const parisDate = '2023-06-22'; + const parisTime = '28:00'; + const utcDatetime = timeConverter.localDateTimeToUtc( + parisDate, + parisTime, + 'Europe/Paris', + ); + expect(utcDatetime).toBeUndefined(); + }); + it('should return undefined if timezone is invalid', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const parisDate = '2023-06-22'; + const parisTime = '08:00'; + const utcDatetime = timeConverter.localDateTimeToUtc( + parisDate, + parisTime, + 'Foo/Bar', + ); + expect(utcDatetime).toBeUndefined(); + }); + it('should return undefined if date is undefined', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const parisDate = undefined; + const parisTime = '08:00'; + const utcDatetime = timeConverter.localDateTimeToUtc( + parisDate, + parisTime, + 'Europe/Paris', + ); + expect(utcDatetime).toBeUndefined(); + }); + }); + + describe('utcDatetimeToLocalTime', () => { + it('should convert an utc datetime isostring to a paris local time', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z'; + const parisTime = timeConverter.utcDatetimeToLocalTime( + utcDatetimeIsostring, + 'Europe/Paris', + ); + expect(parisTime).toBe('08:25'); + }); + it('should return undefined if isostring input is invalid', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const utcDatetimeIsostring = 'not_an_isostring'; + const parisTime = timeConverter.utcDatetimeToLocalTime( + utcDatetimeIsostring, + 'Europe/Paris', + ); + expect(parisTime).toBeUndefined(); + }); + it('should return undefined if timezone input is invalid', () => { + const timeConverter: TimeConverter = new TimeConverter(); + const utcDatetimeIsostring = '2023-06-22T06:25:00.000Z'; + const parisTime = timeConverter.utcDatetimeToLocalTime( + utcDatetimeIsostring, + 'Foo/Bar', + ); + expect(parisTime).toBeUndefined(); + }); + }); +}); diff --git a/src/modules/ad/tests/unit/infrastructure/timezone-finder.spec.ts b/src/modules/ad/tests/unit/infrastructure/timezone-finder.spec.ts new file mode 100644 index 0000000..46e3ab8 --- /dev/null +++ b/src/modules/ad/tests/unit/infrastructure/timezone-finder.spec.ts @@ -0,0 +1,14 @@ +import { TimezoneFinder } from '@modules/ad/infrastructure/timezone-finder'; + +describe('Timezone Finder', () => { + it('should be defined', () => { + const timezoneFinder: TimezoneFinder = new TimezoneFinder(); + expect(timezoneFinder).toBeDefined(); + }); + it('should get timezone for Nancy(France) as Europe/Paris', () => { + const timezoneFinder: TimezoneFinder = new TimezoneFinder(); + const timezones = timezoneFinder.timezones(6.179373, 48.687913); + expect(timezones.length).toBe(1); + expect(timezones[0]).toBe('Europe/Paris'); + }); +}); diff --git a/src/modules/ad/tests/unit/interface/create-ad.grpc.controller.spec.ts b/src/modules/ad/tests/unit/interface/create-ad.grpc.controller.spec.ts new file mode 100644 index 0000000..e0bc8e3 --- /dev/null +++ b/src/modules/ad/tests/unit/interface/create-ad.grpc.controller.spec.ts @@ -0,0 +1,116 @@ +import { IdResponse } from '@libs/api/id.response.dto'; +import { RpcExceptionCode } from '@libs/exceptions/rpc-exception.codes.enum'; +import { AdAlreadyExistsException } from '@modules/ad/core/ad.errors'; +import { Frequency } from '@modules/ad/core/ad.types'; +import { CreateAdGrpcController } from '@modules/ad/interface/grpc-controllers/create-ad.grpc.controller'; +import { CreateAdRequestDto } from '@modules/ad/interface/grpc-controllers/dtos/create-ad.request.dto'; +import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto'; +import { CommandBus } from '@nestjs/cqrs'; +import { RpcException } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; + +const originWaypoint: WaypointDto = { + position: 0, + lon: 48.689445, + lat: 6.17651, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', +}; +const destinationWaypoint: WaypointDto = { + position: 1, + lon: 48.8566, + lat: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', +}; +const punctualCreateAdRequest: CreateAdRequestDto = { + userId: '4eb6a6af-ecfd-41c3-9118-473a507014d4', + fromDate: '2023-12-21', + toDate: '2023-12-21', + schedule: { + thu: '08:15', + }, + driver: false, + passenger: true, + seatsRequested: 1, + frequency: Frequency.PUNCTUAL, + waypoints: [originWaypoint, destinationWaypoint], +}; + +const mockCommandBus = { + execute: jest + .fn() + .mockImplementationOnce(() => '200d61a8-d878-4378-a609-c19ea71633d2') + .mockImplementationOnce(() => { + throw new AdAlreadyExistsException(); + }) + .mockImplementationOnce(() => { + throw new Error(); + }), +}; + +describe('Create Ad Grpc Controller', () => { + let createAdGrpcController: CreateAdGrpcController; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: CommandBus, + useValue: mockCommandBus, + }, + CreateAdGrpcController, + ], + }).compile(); + + createAdGrpcController = module.get( + CreateAdGrpcController, + ); + }); + + afterEach(async () => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(createAdGrpcController).toBeDefined(); + }); + + it('should create a new ad', async () => { + jest.spyOn(mockCommandBus, 'execute'); + const result: IdResponse = await createAdGrpcController.create( + punctualCreateAdRequest, + ); + expect(result).toBeInstanceOf(IdResponse); + expect(result.id).toBe('200d61a8-d878-4378-a609-c19ea71633d2'); + expect(mockCommandBus.execute).toHaveBeenCalledTimes(1); + }); + + it('should throw a dedicated RpcException if ad already exists', async () => { + jest.spyOn(mockCommandBus, 'execute'); + expect.assertions(3); + try { + await createAdGrpcController.create(punctualCreateAdRequest); + } catch (e: any) { + expect(e).toBeInstanceOf(RpcException); + expect(e.error.code).toBe(RpcExceptionCode.ALREADY_EXISTS); + } + expect(mockCommandBus.execute).toHaveBeenCalledTimes(1); + }); + + it('should throw a generic RpcException', async () => { + jest.spyOn(mockCommandBus, 'execute'); + expect.assertions(3); + try { + await createAdGrpcController.create(punctualCreateAdRequest); + } catch (e: any) { + expect(e).toBeInstanceOf(RpcException); + expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN); + } + expect(mockCommandBus.execute).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/modules/ad/tests/unit/interface/find-ad-by-id.grpc.controller.spec.ts b/src/modules/ad/tests/unit/interface/find-ad-by-id.grpc.controller.spec.ts new file mode 100644 index 0000000..b86dd3e --- /dev/null +++ b/src/modules/ad/tests/unit/interface/find-ad-by-id.grpc.controller.spec.ts @@ -0,0 +1,140 @@ +import { NotFoundException } from '@libs/exceptions'; +import { RpcExceptionCode } from '@libs/exceptions/rpc-exception.codes.enum'; +import { AdMapper } from '@modules/ad/ad.mapper'; +import { Frequency } from '@modules/ad/core/ad.types'; +import { FindAdByIdGrpcController } from '@modules/ad/interface/grpc-controllers/find-ad-by-id.grpc.controller'; +import { QueryBus } from '@nestjs/cqrs'; +import { RpcException } from '@nestjs/microservices'; +import { Test, TestingModule } from '@nestjs/testing'; + +const mockQueryBus = { + execute: jest + .fn() + .mockImplementationOnce(() => '200d61a8-d878-4378-a609-c19ea71633d2') + .mockImplementationOnce(() => { + throw new NotFoundException(); + }) + .mockImplementationOnce(() => { + throw new Error(); + }), +}; + +const mockAdMapper = { + toResponse: jest.fn().mockImplementationOnce(() => ({ + userId: '8cc90d1a-4a59-4289-a7d8-078f9db7857f', + driver: true, + passenger: true, + frequency: Frequency.PUNCTUAL, + fromDate: '2023-06-27', + toDate: '2023-06-27', + schedule: { + tue: '07:15', + }, + marginDurations: { + mon: 900, + tue: 900, + wed: 900, + thu: 900, + fri: 900, + sat: 900, + sun: 900, + }, + seatsProposed: 3, + seatsRequested: 1, + waypoints: [ + { + position: 0, + lon: 48.689445, + lat: 6.17651, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + }, + { + position: 1, + lon: 48.8566, + lat: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', + }, + ], + })), +}; + +describe('Find Ad By Id Grpc Controller', () => { + let findAdbyIdGrpcController: FindAdByIdGrpcController; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: QueryBus, + useValue: mockQueryBus, + }, + { + provide: AdMapper, + useValue: mockAdMapper, + }, + FindAdByIdGrpcController, + ], + }).compile(); + + findAdbyIdGrpcController = module.get( + FindAdByIdGrpcController, + ); + }); + + afterEach(async () => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(findAdbyIdGrpcController).toBeDefined(); + }); + + it('should return an ad', async () => { + jest.spyOn(mockQueryBus, 'execute'); + jest.spyOn(mockAdMapper, 'toResponse'); + const response = await findAdbyIdGrpcController.findOnebyId({ + id: '6dcf093c-c7db-4dae-8e9c-c715cebf83c7', + }); + expect(response.userId).toBe('8cc90d1a-4a59-4289-a7d8-078f9db7857f'); + expect(mockQueryBus.execute).toHaveBeenCalledTimes(1); + expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(1); + }); + + it('should throw a dedicated RpcException if ad is not found', async () => { + jest.spyOn(mockQueryBus, 'execute'); + jest.spyOn(mockAdMapper, 'toResponse'); + expect.assertions(4); + try { + await findAdbyIdGrpcController.findOnebyId({ + id: 'ac85f5f4-41cd-4c5d-9aee-0a1acb176fb8', + }); + } catch (e: any) { + expect(e).toBeInstanceOf(RpcException); + expect(e.error.code).toBe(RpcExceptionCode.NOT_FOUND); + } + expect(mockQueryBus.execute).toHaveBeenCalledTimes(1); + expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(0); + }); + + it('should throw a generic RpcException', async () => { + jest.spyOn(mockQueryBus, 'execute'); + jest.spyOn(mockAdMapper, 'toResponse'); + expect.assertions(4); + try { + await findAdbyIdGrpcController.findOnebyId({ + id: '53c8e7ec-ef68-42bc-ba4c-5ef3effa60a6', + }); + } catch (e: any) { + expect(e).toBeInstanceOf(RpcException); + expect(e.error.code).toBe(RpcExceptionCode.UNKNOWN); + } + expect(mockQueryBus.execute).toHaveBeenCalledTimes(1); + expect(mockAdMapper.toResponse).toHaveBeenCalledTimes(0); + }); +}); diff --git a/src/modules/ad/tests/unit/interface/has-valid-position-indexes.decorator.spec.ts b/src/modules/ad/tests/unit/interface/has-valid-position-indexes.decorator.spec.ts new file mode 100644 index 0000000..bf61ce6 --- /dev/null +++ b/src/modules/ad/tests/unit/interface/has-valid-position-indexes.decorator.spec.ts @@ -0,0 +1,62 @@ +import { HasValidPositionIndexes } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/has-valid-position-indexes.decorator'; +import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto'; +import { Validator } from 'class-validator'; + +describe('valid position indexes decorator', () => { + class MyClass { + @HasValidPositionIndexes() + waypoints: WaypointDto[]; + } + it('should return a property decorator has a function', () => { + const hasValidPositionIndexes = HasValidPositionIndexes(); + expect(typeof hasValidPositionIndexes).toBe('function'); + }); + it('should validate an array of waypoints with valid positions', async () => { + const myClassInstance = new MyClass(); + myClassInstance.waypoints = [ + { + position: 0, + lon: 48.8566, + lat: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', + }, + { + position: 1, + lon: 49.2628, + lat: 4.0347, + locality: 'Reims', + postalCode: '51454', + country: 'France', + }, + ]; + const validator = new Validator(); + const validation = await validator.validate(myClassInstance); + expect(validation.length).toBe(0); + }); + it('should not validate an array of waypoints with invalid positions', async () => { + const myClassInstance = new MyClass(); + myClassInstance.waypoints = [ + { + position: 1, + lon: 48.8566, + lat: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', + }, + { + position: 1, + lon: 49.2628, + lat: 4.0347, + locality: 'Reims', + postalCode: '51454', + country: 'France', + }, + ]; + const validator = new Validator(); + const validation = await validator.validate(myClassInstance); + expect(validation.length).toBe(1); + }); +}); diff --git a/src/modules/ad/tests/unit/interface/has-valid-position-indexes.validator.spec.ts b/src/modules/ad/tests/unit/interface/has-valid-position-indexes.validator.spec.ts new file mode 100644 index 0000000..c2a52b0 --- /dev/null +++ b/src/modules/ad/tests/unit/interface/has-valid-position-indexes.validator.spec.ts @@ -0,0 +1,75 @@ +import { hasValidPositionIndexes } from '@modules/ad/interface/grpc-controllers/dtos/validators/has-valid-position-indexes.validator'; +import { WaypointDto } from '@modules/ad/interface/grpc-controllers/dtos/waypoint.dto'; + +describe('addresses position validator', () => { + const mockAddress1: WaypointDto = { + lon: 48.689445, + lat: 6.17651, + houseNumber: '5', + street: 'Avenue Foch', + locality: 'Nancy', + postalCode: '54000', + country: 'France', + }; + const mockAddress2: WaypointDto = { + lon: 48.8566, + lat: 2.3522, + locality: 'Paris', + postalCode: '75000', + country: 'France', + }; + const mockAddress3: WaypointDto = { + lon: 49.2628, + lat: 4.0347, + locality: 'Reims', + postalCode: '51454', + country: 'France', + }; + + it('should not validate if no position is defined', () => { + expect( + hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeFalsy(); + }); + it('should not validate if only one position is defined', () => { + mockAddress1.position = 0; + expect( + hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeFalsy(); + }); + it('should not validate if positions are partially defined', () => { + mockAddress1.position = 0; + mockAddress2.position = null; + mockAddress3.position = undefined; + expect( + hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeFalsy(); + }); + + it('should not validate if multiple positions have same value', () => { + mockAddress1.position = 0; + mockAddress2.position = 1; + mockAddress3.position = 1; + expect( + hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeFalsy(); + }); + it('should validate if all positions are ordered', () => { + mockAddress1.position = 0; + mockAddress2.position = 1; + mockAddress3.position = 2; + expect( + hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeTruthy(); + mockAddress1.position = 1; + mockAddress2.position = 2; + mockAddress3.position = 3; + expect( + hasValidPositionIndexes([mockAddress1, mockAddress2, mockAddress3]), + ).toBeTruthy(); + }); + it('should not validate if no waypoints are defined', () => { + expect(hasValidPositionIndexes(undefined)).toBeFalsy(); + expect(hasValidPositionIndexes([])).toBeFalsy(); + }); +}); diff --git a/src/modules/ad/tests/unit/interface/int-to-frequency.spec.ts b/src/modules/ad/tests/unit/interface/int-to-frequency.spec.ts new file mode 100644 index 0000000..08f2729 --- /dev/null +++ b/src/modules/ad/tests/unit/interface/int-to-frequency.spec.ts @@ -0,0 +1,15 @@ +import { Frequency } from '@modules/ad/core/ad.types'; +import { intToFrequency } from '@modules/ad/interface/grpc-controllers/dtos/transformers/int-to-frequency'; + +describe('frequency mapping', () => { + it('should return punctual if frequency is 1', () => { + expect(intToFrequency(1)).toBe(Frequency.PUNCTUAL); + }); + it('should return recurrent if frequency is 2', () => { + expect(intToFrequency(2)).toBe(Frequency.RECURRENT); + }); + it('should throw an error if frequency is unknown', () => { + expect(() => intToFrequency(0)).toThrow(); + expect(() => intToFrequency(3)).toThrow(); + }); +}); diff --git a/src/modules/ad/tests/unit/interface/is-schedule.decorator.spec.ts b/src/modules/ad/tests/unit/interface/is-schedule.decorator.spec.ts new file mode 100644 index 0000000..613d0b4 --- /dev/null +++ b/src/modules/ad/tests/unit/interface/is-schedule.decorator.spec.ts @@ -0,0 +1,30 @@ +import { ScheduleDto } from '@modules/ad/interface/grpc-controllers/dtos/schedule.dto'; +import { IsSchedule } from '@modules/ad/interface/grpc-controllers/dtos/validators/decorators/is-schedule.decorator'; +import { Validator } from 'class-validator'; + +describe('schedule decorator', () => { + class MyClass { + @IsSchedule() + schedule: ScheduleDto; + } + it('should return a property decorator has a function', () => { + const isSchedule = IsSchedule(); + expect(typeof isSchedule).toBe('function'); + }); + it('should validate a valid schedule', async () => { + const myClassInstance = new MyClass(); + myClassInstance.schedule = { + mon: '07:15', + }; + const validator = new Validator(); + const validation = await validator.validate(myClassInstance); + expect(validation.length).toBe(0); + }); + it('should not validate a invalid schedule', async () => { + const myClassInstance = new MyClass(); + myClassInstance.schedule = {}; + const validator = new Validator(); + const validation = await validator.validate(myClassInstance); + expect(validation.length).toBe(1); + }); +}); diff --git a/src/modules/ad/tests/unit/interface/to-precision.spec.ts b/src/modules/ad/tests/unit/interface/to-precision.spec.ts new file mode 100644 index 0000000..2da0933 --- /dev/null +++ b/src/modules/ad/tests/unit/interface/to-precision.spec.ts @@ -0,0 +1,14 @@ +import { toPrecision } from '@modules/ad/interface/grpc-controllers/dtos/transformers/to-precision'; + +describe('precision handler', () => { + it('should return a 6 digits float number for a 10 digits float input number and 6 as precision', () => { + const precised = toPrecision(1.1234567891, 6); + const stringPrecised = precised.toString().split('.')[1]; + expect(stringPrecised.length).toBe(6); + }); + it('should return a 2 digits float number for a 2 digits float input number and 4 as precision', () => { + const precised = toPrecision(1.12, 4); + const stringPrecised = precised.toString().split('.')[1]; + expect(stringPrecised.length).toBe(2); + }); +}); diff --git a/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts b/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts deleted file mode 100644 index b5b9700..0000000 --- a/src/modules/database/adapters/secondaries/prisma-repository.abstract.ts +++ /dev/null @@ -1,258 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Prisma } from '@prisma/client'; -import { DatabaseException } from '../../exceptions/database.exception'; -import { ICollection } from '../../interfaces/collection.interface'; -import { IRepository } from '../../interfaces/repository.interface'; -import { PrismaService } from './prisma-service'; - -/** - * Child classes MUST redefined model property with appropriate model name - */ -@Injectable() -export abstract class PrismaRepository implements IRepository { - protected model: string; - - constructor(protected readonly _prisma: PrismaService) {} - - async findAll( - page = 1, - perPage = 10, - where?: any, - include?: any, - ): Promise> { - const [data, total] = await this._prisma.$transaction([ - this._prisma[this.model].findMany({ - where, - include, - skip: (page - 1) * perPage, - take: perPage, - }), - this._prisma[this.model].count({ - where, - }), - ]); - return Promise.resolve({ - data, - total, - }); - } - - async findOneByUuid(uuid: string): Promise { - try { - const entity = await this._prisma[this.model].findUnique({ - where: { uuid }, - }); - - return entity; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async findOne(where: any, include?: any): Promise { - try { - const entity = await this._prisma[this.model].findFirst({ - where: where, - include: include, - }); - - return entity; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - ); - } else { - throw new DatabaseException(); - } - } - } - - // TODO : using any is not good, but needed for nested entities - // TODO : Refactor for good clean architecture ? - async create(entity: Partial | any, include?: any): Promise { - try { - const res = await this._prisma[this.model].create({ - data: entity, - include: include, - }); - return res; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async update(uuid: string, entity: Partial): Promise { - try { - const updatedEntity = await this._prisma[this.model].update({ - where: { uuid }, - data: entity, - }); - return updatedEntity; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async updateWhere( - where: any, - entity: Partial | any, - include?: any, - ): Promise { - try { - const updatedEntity = await this._prisma[this.model].update({ - where: where, - data: entity, - include: include, - }); - - return updatedEntity; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async delete(uuid: string): Promise { - try { - const entity = await this._prisma[this.model].delete({ - where: { uuid }, - }); - - return entity; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async deleteMany(where: any): Promise { - try { - const entity = await this._prisma[this.model].deleteMany({ - where: where, - }); - - return entity; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async findAllByQuery( - include: string[], - where: string[], - ): Promise> { - const query = `SELECT ${include.join(',')} FROM ${ - this.model - } WHERE ${where.join(' AND ')}`; - const data: T[] = await this._prisma.$queryRawUnsafe(query); - return Promise.resolve({ - data, - total: data.length, - }); - } - - async createWithFields(fields: object): Promise { - try { - const command = `INSERT INTO ${this.model} ("${Object.keys(fields).join( - '","', - )}") VALUES (${Object.values(fields).join(',')})`; - return await this._prisma.$executeRawUnsafe(command); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async updateWithFields(uuid: string, entity: object): Promise { - entity['"updatedAt"'] = `to_timestamp(${Date.now()} / 1000.0)`; - const values = Object.keys(entity).map((key) => `${key} = ${entity[key]}`); - try { - const command = `UPDATE ${this.model} SET ${values.join( - ', ', - )} WHERE uuid = '${uuid}'`; - return await this._prisma.$executeRawUnsafe(command); - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } - - async healthCheck(): Promise { - try { - await this._prisma.$queryRaw`SELECT 1`; - return true; - } catch (e) { - if (e instanceof Prisma.PrismaClientKnownRequestError) { - throw new DatabaseException( - Prisma.PrismaClientKnownRequestError.name, - e.code, - e.message, - ); - } else { - throw new DatabaseException(); - } - } - } -} diff --git a/src/modules/database/database.module.ts b/src/modules/database/database.module.ts deleted file mode 100644 index f1defa7..0000000 --- a/src/modules/database/database.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { PrismaService } from './adapters/secondaries/prisma-service'; -import { AdRepository } from './domain/ad-repository'; - -@Module({ - providers: [PrismaService, AdRepository], - exports: [PrismaService, AdRepository], -}) -export class DatabaseModule {} diff --git a/src/modules/database/domain/ad-repository.ts b/src/modules/database/domain/ad-repository.ts deleted file mode 100644 index edbaf5f..0000000 --- a/src/modules/database/domain/ad-repository.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { PrismaRepository } from '../adapters/secondaries/prisma-repository.abstract'; - -export class AdRepository extends PrismaRepository {} diff --git a/src/modules/database/exceptions/database.exception.ts b/src/modules/database/exceptions/database.exception.ts deleted file mode 100644 index b0782a6..0000000 --- a/src/modules/database/exceptions/database.exception.ts +++ /dev/null @@ -1,24 +0,0 @@ -export class DatabaseException implements Error { - name: string; - message: string; - - constructor( - private _type: string = 'unknown', - private _code: string = '', - message?: string, - ) { - this.name = 'DatabaseException'; - this.message = message ?? 'An error occured with the database.'; - if (this.message.includes('Unique constraint failed')) { - this.message = 'Already exists.'; - } - } - - get type(): string { - return this._type; - } - - get code(): string { - return this._code; - } -} diff --git a/src/modules/database/interfaces/collection.interface.ts b/src/modules/database/interfaces/collection.interface.ts deleted file mode 100644 index 6e9a96d..0000000 --- a/src/modules/database/interfaces/collection.interface.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface ICollection { - data: T[]; - total: number; -} diff --git a/src/modules/database/interfaces/repository.interface.ts b/src/modules/database/interfaces/repository.interface.ts deleted file mode 100644 index 1e23984..0000000 --- a/src/modules/database/interfaces/repository.interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ICollection } from './collection.interface'; - -export interface IRepository { - findAll( - page: number, - perPage: number, - params?: any, - include?: any, - ): Promise>; - findOne(where: any, include?: any): Promise; - findOneByUuid(uuid: string, include?: any): Promise; - create(entity: Partial | any, include?: any): Promise; - update(uuid: string, entity: Partial, include?: any): Promise; - updateWhere(where: any, entity: Partial | any, include?: any): Promise; - delete(uuid: string): Promise; - deleteMany(where: any): Promise; - healthCheck(): Promise; -} diff --git a/src/modules/database/tests/unit/prisma-repository.spec.ts b/src/modules/database/tests/unit/prisma-repository.spec.ts deleted file mode 100644 index eb3bad0..0000000 --- a/src/modules/database/tests/unit/prisma-repository.spec.ts +++ /dev/null @@ -1,571 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import { PrismaService } from '../../adapters/secondaries/prisma-service'; -import { PrismaRepository } from '../../adapters/secondaries/prisma-repository.abstract'; -import { DatabaseException } from '../../exceptions/database.exception'; -import { Prisma } from '@prisma/client'; - -class FakeEntity { - uuid?: string; - name: string; -} - -let entityId = 2; -const entityUuid = 'uuid-'; -const entityName = 'name-'; - -const createRandomEntity = (): FakeEntity => { - const entity: FakeEntity = { - uuid: `${entityUuid}${entityId}`, - name: `${entityName}${entityId}`, - }; - - entityId++; - - return entity; -}; - -const fakeEntityToCreate: FakeEntity = { - name: 'test', -}; - -const fakeEntityCreated: FakeEntity = { - ...fakeEntityToCreate, - uuid: 'some-uuid', -}; - -const fakeEntities: FakeEntity[] = []; -Array.from({ length: 10 }).forEach(() => { - fakeEntities.push(createRandomEntity()); -}); - -@Injectable() -class FakePrismaRepository extends PrismaRepository { - protected model = 'fake'; -} - -class FakePrismaService extends PrismaService { - fake: any; -} - -const mockPrismaService = { - $transaction: jest.fn().mockImplementation(async (data: any) => { - const entities = await data[0]; - if (entities.length == 1) { - return Promise.resolve([[fakeEntityCreated], 1]); - } - - return Promise.resolve([fakeEntities, fakeEntities.length]); - }), - // eslint-disable-next-line @typescript-eslint/no-unused-vars - $queryRawUnsafe: jest.fn().mockImplementation((query?: string) => { - return Promise.resolve(fakeEntities); - }), - $executeRawUnsafe: jest - .fn() - .mockResolvedValueOnce(fakeEntityCreated) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((fields: object) => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((fields: object) => { - throw new Error('an unknown error'); - }) - .mockResolvedValueOnce(fakeEntityCreated) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((fields: object) => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((fields: object) => { - throw new Error('an unknown error'); - }), - $queryRaw: jest - .fn() - .mockImplementationOnce(() => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - .mockImplementationOnce(() => { - return true; - }) - .mockImplementation(() => { - throw new Prisma.PrismaClientKnownRequestError('Database unavailable', { - code: 'code', - clientVersion: 'version', - }); - }), - fake: { - create: jest - .fn() - .mockResolvedValueOnce(fakeEntityCreated) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((params?: any) => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((params?: any) => { - throw new Error('an unknown error'); - }), - - findMany: jest.fn().mockImplementation((params?: any) => { - if (params?.where?.limit == 1) { - return Promise.resolve([fakeEntityCreated]); - } - - return Promise.resolve(fakeEntities); - }), - count: jest.fn().mockResolvedValue(fakeEntities.length), - - findUnique: jest.fn().mockImplementation(async (params?: any) => { - let entity; - - if (params?.where?.uuid) { - entity = fakeEntities.find( - (entity) => entity.uuid === params?.where?.uuid, - ); - } - - if (!entity && params?.where?.uuid == 'unknown') { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - } else if (!entity) { - throw new Error('no entity'); - } - - return entity; - }), - - findFirst: jest - .fn() - .mockImplementationOnce((params?: any) => { - if (params?.where?.name) { - return Promise.resolve( - fakeEntities.find((entity) => entity.name === params?.where?.name), - ); - } - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((params?: any) => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((params?: any) => { - throw new Error('an unknown error'); - }), - - update: jest - .fn() - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((params?: any) => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((params?: any) => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - .mockImplementationOnce((params: any) => { - const entity = fakeEntities.find( - (entity) => entity.name === params.where.name, - ); - Object.entries(params.data).map(([key, value]) => { - entity[key] = value; - }); - - return Promise.resolve(entity); - }) - .mockImplementation((params: any) => { - const entity = fakeEntities.find( - (entity) => entity.uuid === params.where.uuid, - ); - Object.entries(params.data).map(([key, value]) => { - entity[key] = value; - }); - - return Promise.resolve(entity); - }), - - delete: jest - .fn() - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((params?: any) => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - .mockImplementation((params: any) => { - let found = false; - - fakeEntities.forEach((entity, index) => { - if (entity.uuid === params?.where?.uuid) { - found = true; - fakeEntities.splice(index, 1); - } - }); - - if (!found) { - throw new Error(); - } - }), - - deleteMany: jest - .fn() - // eslint-disable-next-line @typescript-eslint/no-unused-vars - .mockImplementationOnce((params?: any) => { - throw new Prisma.PrismaClientKnownRequestError('unknown request', { - code: 'code', - clientVersion: 'version', - }); - }) - .mockImplementation((params: any) => { - let found = false; - - fakeEntities.forEach((entity, index) => { - if (entity.uuid === params?.where?.uuid) { - found = true; - fakeEntities.splice(index, 1); - } - }); - - if (!found) { - throw new Error(); - } - }), - }, -}; - -describe('PrismaRepository', () => { - let fakeRepository: FakePrismaRepository; - let prisma: FakePrismaService; - - beforeAll(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - FakePrismaRepository, - { - provide: PrismaService, - useValue: mockPrismaService, - }, - ], - }).compile(); - - fakeRepository = module.get(FakePrismaRepository); - prisma = module.get(PrismaService) as FakePrismaService; - }); - - it('should be defined', () => { - expect(fakeRepository).toBeDefined(); - expect(prisma).toBeDefined(); - }); - - describe('findAll', () => { - it('should return an array of entities', async () => { - jest.spyOn(prisma.fake, 'findMany'); - jest.spyOn(prisma.fake, 'count'); - jest.spyOn(prisma, '$transaction'); - - const entities = await fakeRepository.findAll(); - expect(entities).toStrictEqual({ - data: fakeEntities, - total: fakeEntities.length, - }); - }); - - it('should return an array containing only one entity', async () => { - const entities = await fakeRepository.findAll(1, 10, { limit: 1 }); - - expect(prisma.fake.findMany).toHaveBeenCalledWith({ - skip: 0, - take: 10, - where: { limit: 1 }, - }); - expect(entities).toEqual({ - data: [fakeEntityCreated], - total: 1, - }); - }); - }); - - describe('create', () => { - it('should create an entity', async () => { - jest.spyOn(prisma.fake, 'create'); - - const newEntity = await fakeRepository.create(fakeEntityToCreate); - expect(newEntity).toBe(fakeEntityCreated); - expect(prisma.fake.create).toHaveBeenCalledTimes(1); - }); - - it('should throw a DatabaseException for client error', async () => { - await expect( - fakeRepository.create(fakeEntityToCreate), - ).rejects.toBeInstanceOf(DatabaseException); - }); - - it('should throw a DatabaseException if uuid is not found', async () => { - await expect( - fakeRepository.create(fakeEntityToCreate), - ).rejects.toBeInstanceOf(DatabaseException); - }); - }); - - describe('findOneByUuid', () => { - it('should find an entity by uuid', async () => { - const entity = await fakeRepository.findOneByUuid(fakeEntities[0].uuid); - expect(entity).toBe(fakeEntities[0]); - }); - - it('should throw a DatabaseException for client error', async () => { - await expect( - fakeRepository.findOneByUuid('unknown'), - ).rejects.toBeInstanceOf(DatabaseException); - }); - - it('should throw a DatabaseException if uuid is not found', async () => { - await expect( - fakeRepository.findOneByUuid('wrong-uuid'), - ).rejects.toBeInstanceOf(DatabaseException); - }); - }); - - describe('findOne', () => { - it('should find one entity', async () => { - const entity = await fakeRepository.findOne({ - name: fakeEntities[0].name, - }); - - expect(entity.name).toBe(fakeEntities[0].name); - }); - - it('should throw a DatabaseException for client error', async () => { - await expect( - fakeRepository.findOne({ - name: fakeEntities[0].name, - }), - ).rejects.toBeInstanceOf(DatabaseException); - }); - - it('should throw a DatabaseException for unknown error', async () => { - await expect( - fakeRepository.findOne({ - name: fakeEntities[0].name, - }), - ).rejects.toBeInstanceOf(DatabaseException); - }); - }); - - describe('update', () => { - it('should throw a DatabaseException for client error', async () => { - await expect( - fakeRepository.update('fake-uuid', { name: 'error' }), - ).rejects.toBeInstanceOf(DatabaseException); - await expect( - fakeRepository.updateWhere({ name: 'error' }, { name: 'new error' }), - ).rejects.toBeInstanceOf(DatabaseException); - }); - - it('should update an entity with name', async () => { - const newName = 'new-random-name'; - - await fakeRepository.updateWhere( - { name: fakeEntities[0].name }, - { - name: newName, - }, - ); - expect(fakeEntities[0].name).toBe(newName); - }); - - it('should update an entity with uuid', async () => { - const newName = 'random-name'; - - await fakeRepository.update(fakeEntities[0].uuid, { - name: newName, - }); - expect(fakeEntities[0].name).toBe(newName); - }); - - it("should throw an exception if an entity doesn't exist", async () => { - await expect( - fakeRepository.update('fake-uuid', { name: 'error' }), - ).rejects.toBeInstanceOf(DatabaseException); - await expect( - fakeRepository.updateWhere({ name: 'error' }, { name: 'new error' }), - ).rejects.toBeInstanceOf(DatabaseException); - }); - }); - - describe('delete', () => { - it('should throw a DatabaseException for client error', async () => { - await expect(fakeRepository.delete('fake-uuid')).rejects.toBeInstanceOf( - DatabaseException, - ); - }); - - it('should delete an entity', async () => { - const savedUuid = fakeEntities[0].uuid; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const res = await fakeRepository.delete(savedUuid); - - const deletedEntity = fakeEntities.find( - (entity) => entity.uuid === savedUuid, - ); - expect(deletedEntity).toBeUndefined(); - }); - - it("should throw an exception if an entity doesn't exist", async () => { - await expect(fakeRepository.delete('fake-uuid')).rejects.toBeInstanceOf( - DatabaseException, - ); - }); - }); - - describe('deleteMany', () => { - it('should throw a DatabaseException for client error', async () => { - await expect( - fakeRepository.deleteMany({ uuid: 'fake-uuid' }), - ).rejects.toBeInstanceOf(DatabaseException); - }); - - it('should delete entities based on their uuid', async () => { - const savedUuid = fakeEntities[0].uuid; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const res = await fakeRepository.deleteMany({ uuid: savedUuid }); - - const deletedEntity = fakeEntities.find( - (entity) => entity.uuid === savedUuid, - ); - expect(deletedEntity).toBeUndefined(); - }); - - it("should throw an exception if an entity doesn't exist", async () => { - await expect( - fakeRepository.deleteMany({ uuid: 'fake-uuid' }), - ).rejects.toBeInstanceOf(DatabaseException); - }); - }); - - describe('findAllByquery', () => { - it('should return an array of entities', async () => { - const entities = await fakeRepository.findAllByQuery( - ['uuid', 'name'], - ['name is not null'], - ); - expect(entities).toStrictEqual({ - data: fakeEntities, - total: fakeEntities.length, - }); - }); - }); - - describe('createWithFields', () => { - it('should create an entity', async () => { - jest.spyOn(prisma, '$queryRawUnsafe'); - - const newEntity = await fakeRepository.createWithFields({ - uuid: '804319b3-a09b-4491-9f82-7976bfce0aff', - name: 'my-name', - }); - expect(newEntity).toBe(fakeEntityCreated); - expect(prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1); - }); - - it('should throw a DatabaseException for client error', async () => { - await expect( - fakeRepository.createWithFields({ - uuid: '804319b3-a09b-4491-9f82-7976bfce0aff', - name: 'my-name', - }), - ).rejects.toBeInstanceOf(DatabaseException); - }); - - it('should throw a DatabaseException if uuid is not found', async () => { - await expect( - fakeRepository.createWithFields({ - name: 'my-name', - }), - ).rejects.toBeInstanceOf(DatabaseException); - }); - }); - - describe('updateWithFields', () => { - it('should update an entity', async () => { - jest.spyOn(prisma, '$queryRawUnsafe'); - - const updatedEntity = await fakeRepository.updateWithFields( - '804319b3-a09b-4491-9f82-7976bfce0aff', - { - name: 'my-name', - }, - ); - expect(updatedEntity).toBe(fakeEntityCreated); - expect(prisma.$queryRawUnsafe).toHaveBeenCalledTimes(1); - }); - - it('should throw a DatabaseException for client error', async () => { - await expect( - fakeRepository.updateWithFields( - '804319b3-a09b-4491-9f82-7976bfce0aff', - { - name: 'my-name', - }, - ), - ).rejects.toBeInstanceOf(DatabaseException); - }); - - it('should throw a DatabaseException if uuid is not found', async () => { - await expect( - fakeRepository.updateWithFields( - '804319b3-a09b-4491-9f82-7976bfce0aff', - { - name: 'my-name', - }, - ), - ).rejects.toBeInstanceOf(DatabaseException); - }); - }); - - describe('healthCheck', () => { - it('should throw a DatabaseException for client error', async () => { - await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf( - DatabaseException, - ); - }); - - it('should return a healthy result', async () => { - const res = await fakeRepository.healthCheck(); - expect(res).toBeTruthy(); - }); - - it('should throw an exception if database is not available', async () => { - await expect(fakeRepository.healthCheck()).rejects.toBeInstanceOf( - DatabaseException, - ); - }); - }); -}); diff --git a/src/modules/health/adapters/primaries/health.controller.ts b/src/modules/health/adapters/primaries/health.controller.ts deleted file mode 100644 index ba6ad9f..0000000 --- a/src/modules/health/adapters/primaries/health.controller.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Controller, Get, Inject } from '@nestjs/common'; -import { - HealthCheckService, - HealthCheck, - HealthCheckResult, -} from '@nestjs/terminus'; -import { MESSAGE_PUBLISHER } from 'src/app.constants'; -import { IPublishMessage } from 'src/interfaces/message-publisher'; -import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase'; - -@Controller('health') -export class HealthController { - constructor( - private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase, - private healthCheckService: HealthCheckService, - @Inject(MESSAGE_PUBLISHER) - private readonly messagePublisher: IPublishMessage, - ) {} - - @Get() - @HealthCheck() - async check() { - try { - return await this.healthCheckService.check([ - async () => - this.repositoriesHealthIndicatorUseCase.isHealthy('repositories'), - ]); - } catch (error) { - const healthCheckResult: HealthCheckResult = error.response; - this.messagePublisher.publish( - 'logging.user.health.crit', - JSON.stringify(healthCheckResult.error), - ); - throw error; - } - } -} diff --git a/src/modules/health/core/ports/check-repository.port.ts b/src/modules/health/core/ports/check-repository.port.ts new file mode 100644 index 0000000..64d8980 --- /dev/null +++ b/src/modules/health/core/ports/check-repository.port.ts @@ -0,0 +1,3 @@ +export interface CheckRepositoryPort { + healthCheck(): Promise; +} diff --git a/src/modules/health/core/usecases/repositories.health-indicator.usecase.ts b/src/modules/health/core/usecases/repositories.health-indicator.usecase.ts new file mode 100644 index 0000000..255263b --- /dev/null +++ b/src/modules/health/core/usecases/repositories.health-indicator.usecase.ts @@ -0,0 +1,48 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { + HealthCheckError, + HealthCheckResult, + HealthIndicator, + HealthIndicatorResult, +} from '@nestjs/terminus'; +import { CheckRepositoryPort } from '../ports/check-repository.port'; +import { AD_REPOSITORY } from '@modules/health/health.di-tokens'; +import { AdRepositoryPort } from '@modules/ad/core/ports/ad.repository.port'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; +import { LOGGING_AD_HEALTH_CRIT } from '@modules/health/health.constants'; +import { MessagePublisherPort } from '@libs/ports/message-publisher.port'; + +@Injectable() +export class RepositoriesHealthIndicatorUseCase extends HealthIndicator { + private _checkRepositories: CheckRepositoryPort[]; + constructor( + @Inject(AD_REPOSITORY) + private readonly adRepository: AdRepositoryPort, + @Inject(MESSAGE_PUBLISHER) + private readonly messagePublisher: MessagePublisherPort, + ) { + super(); + this._checkRepositories = [adRepository]; + } + isHealthy = async (key: string): Promise => { + try { + await Promise.all( + this._checkRepositories.map( + async (checkRepository: CheckRepositoryPort) => { + await checkRepository.healthCheck(); + }, + ), + ); + return this.getStatus(key, true); + } catch (error) { + const healthCheckResult: HealthCheckResult = error; + this.messagePublisher.publish( + LOGGING_AD_HEALTH_CRIT, + JSON.stringify(healthCheckResult.error), + ); + throw new HealthCheckError('Repository', { + repository: error.message, + }); + } + }; +} diff --git a/src/modules/health/domain/interfaces/check-repository.interface.ts b/src/modules/health/domain/interfaces/check-repository.interface.ts deleted file mode 100644 index 68c3178..0000000 --- a/src/modules/health/domain/interfaces/check-repository.interface.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface ICheckRepository { - healthCheck(): Promise; -} diff --git a/src/modules/health/domain/usecases/repositories.health-indicator.usecase.ts b/src/modules/health/domain/usecases/repositories.health-indicator.usecase.ts deleted file mode 100644 index f4db828..0000000 --- a/src/modules/health/domain/usecases/repositories.health-indicator.usecase.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { - HealthCheckError, - HealthIndicator, - HealthIndicatorResult, -} from '@nestjs/terminus'; -import { ICheckRepository } from '../interfaces/check-repository.interface'; -import { AdsRepository } from '../../../ad/adapters/secondaries/ads.repository'; - -@Injectable() -export class RepositoriesHealthIndicatorUseCase extends HealthIndicator { - private checkRepositories: ICheckRepository[]; - constructor(private readonly adsRepository: AdsRepository) { - super(); - this.checkRepositories = [adsRepository]; - } - isHealthy = async (key: string): Promise => { - try { - await Promise.all( - this.checkRepositories.map( - async (checkRepository: ICheckRepository) => { - await checkRepository.healthCheck(); - }, - ), - ); - return this.getStatus(key, true); - } catch (e: any) { - throw new HealthCheckError('Repository', { - repository: e.message, - }); - } - }; -} diff --git a/src/modules/health/health.constants.ts b/src/modules/health/health.constants.ts new file mode 100644 index 0000000..3f29432 --- /dev/null +++ b/src/modules/health/health.constants.ts @@ -0,0 +1 @@ +export const LOGGING_AD_HEALTH_CRIT = 'logging.ad.health.crit'; diff --git a/src/modules/health/health.di-tokens.ts b/src/modules/health/health.di-tokens.ts new file mode 100644 index 0000000..2706306 --- /dev/null +++ b/src/modules/health/health.di-tokens.ts @@ -0,0 +1 @@ +export const AD_REPOSITORY = Symbol('AD_REPOSITORY'); diff --git a/src/modules/health/health.module.ts b/src/modules/health/health.module.ts index de1bc71..988cd14 100644 --- a/src/modules/health/health.module.ts +++ b/src/modules/health/health.module.ts @@ -1,28 +1,42 @@ -import { Module } from '@nestjs/common'; -import { HealthServerController } from './adapters/primaries/health-server.controller'; -import { AdsRepository } from '../ad/adapters/secondaries/ads.repository'; -import { DatabaseModule } from '../database/database.module'; -import { HealthController } from './adapters/primaries/health.controller'; +import { Module, Provider } from '@nestjs/common'; +import { HealthHttpController } from './interface/http-controllers/health.http.controller'; import { TerminusModule } from '@nestjs/terminus'; import { MESSAGE_BROKER_PUBLISHER, MESSAGE_PUBLISHER } from 'src/app.constants'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; -import { MessagePublisher } from './adapters/secondaries/message-publisher'; -import { RepositoriesHealthIndicatorUseCase } from './domain/usecases/repositories.health-indicator.usecase'; +import { MessagePublisher } from './infrastructure/message-publisher'; +import { RepositoriesHealthIndicatorUseCase } from './core/usecases/repositories.health-indicator.usecase'; +import { AdRepository } from '../ad/infrastructure/ad.repository'; +import { AD_REPOSITORY } from './health.di-tokens'; +import { HealthGrpcController } from './interface/grpc-controllers/health.grpc.controller'; +import { AdModule } from '@modules/ad/ad.module'; + +const grpcControllers = [HealthGrpcController]; + +const httpControllers = [HealthHttpController]; + +const useCases: Provider[] = [RepositoriesHealthIndicatorUseCase]; + +const repositories: Provider[] = [ + { + provide: AD_REPOSITORY, + useClass: AdRepository, + }, +]; + +const messageBrokers: Provider[] = [ + { + provide: MESSAGE_BROKER_PUBLISHER, + useClass: MessageBrokerPublisher, + }, + { + provide: MESSAGE_PUBLISHER, + useClass: MessagePublisher, + }, +]; @Module({ - imports: [TerminusModule, DatabaseModule], - controllers: [HealthServerController, HealthController], - providers: [ - RepositoriesHealthIndicatorUseCase, - AdsRepository, - { - provide: MESSAGE_BROKER_PUBLISHER, - useClass: MessageBrokerPublisher, - }, - { - provide: MESSAGE_PUBLISHER, - useClass: MessagePublisher, - }, - ], + imports: [TerminusModule, AdModule], + controllers: [...grpcControllers, ...httpControllers], + providers: [...useCases, ...repositories, ...messageBrokers], }) export class HealthModule {} diff --git a/src/modules/ad/adapters/secondaries/message-publisher.ts b/src/modules/health/infrastructure/message-publisher.ts similarity index 66% rename from src/modules/ad/adapters/secondaries/message-publisher.ts rename to src/modules/health/infrastructure/message-publisher.ts index 98a963b..036743b 100644 --- a/src/modules/ad/adapters/secondaries/message-publisher.ts +++ b/src/modules/health/infrastructure/message-publisher.ts @@ -1,10 +1,10 @@ import { Inject, Injectable } from '@nestjs/common'; -import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants'; +import { MESSAGE_BROKER_PUBLISHER } from '../../../app.constants'; import { MessageBrokerPublisher } from '@mobicoop/message-broker-module'; -import { IPublishMessage } from 'src/interfaces/message-publisher'; +import { MessagePublisherPort } from '@libs/ports/message-publisher.port'; @Injectable() -export class MessagePublisher implements IPublishMessage { +export class MessagePublisher implements MessagePublisherPort { constructor( @Inject(MESSAGE_BROKER_PUBLISHER) private readonly messageBrokerPublisher: MessageBrokerPublisher, diff --git a/src/modules/health/adapters/primaries/health-server.controller.ts b/src/modules/health/interface/grpc-controllers/health.grpc.controller.ts similarity index 79% rename from src/modules/health/adapters/primaries/health-server.controller.ts rename to src/modules/health/interface/grpc-controllers/health.grpc.controller.ts index 3cdc70d..f939dd2 100644 --- a/src/modules/health/adapters/primaries/health-server.controller.ts +++ b/src/modules/health/interface/grpc-controllers/health.grpc.controller.ts @@ -1,8 +1,8 @@ import { Controller } from '@nestjs/common'; import { GrpcMethod } from '@nestjs/microservices'; -import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase'; +import { RepositoriesHealthIndicatorUseCase } from '../../core/usecases/repositories.health-indicator.usecase'; -enum ServingStatus { +export enum ServingStatus { UNKNOWN = 0, SERVING = 1, NOT_SERVING = 2, @@ -17,7 +17,7 @@ interface HealthCheckResponse { } @Controller() -export class HealthServerController { +export class HealthGrpcController { constructor( private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase, ) {} @@ -25,9 +25,9 @@ export class HealthServerController { @GrpcMethod('Health', 'Check') async check( // eslint-disable-next-line @typescript-eslint/no-unused-vars - data: HealthCheckRequest, + data?: HealthCheckRequest, // eslint-disable-next-line @typescript-eslint/no-unused-vars - metadata: any, + metadata?: any, ): Promise { const healthCheck = await this.repositoriesHealthIndicatorUseCase.isHealthy( 'repositories', diff --git a/src/modules/health/adapters/primaries/health.proto b/src/modules/health/interface/grpc-controllers/health.proto similarity index 100% rename from src/modules/health/adapters/primaries/health.proto rename to src/modules/health/interface/grpc-controllers/health.proto diff --git a/src/modules/health/interface/http-controllers/health.http.controller.ts b/src/modules/health/interface/http-controllers/health.http.controller.ts new file mode 100644 index 0000000..256dae5 --- /dev/null +++ b/src/modules/health/interface/http-controllers/health.http.controller.ts @@ -0,0 +1,28 @@ +import { RepositoriesHealthIndicatorUseCase } from '@modules/health/core/usecases/repositories.health-indicator.usecase'; +import { Controller, Get } from '@nestjs/common'; +import { + HealthCheckService, + HealthCheck, + HealthCheckResult, +} from '@nestjs/terminus'; + +@Controller('health') +export class HealthHttpController { + constructor( + private readonly repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase, + private readonly healthCheckService: HealthCheckService, + ) {} + + @Get() + @HealthCheck() + async check(): Promise { + try { + return await this.healthCheckService.check([ + async () => + this.repositoriesHealthIndicatorUseCase.isHealthy('repositories'), + ]); + } catch (error) { + throw error; + } + } +} diff --git a/src/modules/health/tests/unit/health.grpc.controller.spec.ts b/src/modules/health/tests/unit/health.grpc.controller.spec.ts new file mode 100644 index 0000000..0157218 --- /dev/null +++ b/src/modules/health/tests/unit/health.grpc.controller.spec.ts @@ -0,0 +1,72 @@ +import { RepositoriesHealthIndicatorUseCase } from '@modules/health/core/usecases/repositories.health-indicator.usecase'; +import { + HealthGrpcController, + ServingStatus, +} from '@modules/health/interface/grpc-controllers/health.grpc.controller'; +import { Test, TestingModule } from '@nestjs/testing'; + +const mockRepositoriesHealthIndicatorUseCase = { + isHealthy: jest + .fn() + .mockImplementationOnce(() => ({ + repositories: { + status: 'up', + }, + })) + .mockImplementationOnce(() => ({ + repositories: { + status: 'down', + }, + })), +}; + +describe('Health Grpc Controller', () => { + let healthGrpcController: HealthGrpcController; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: RepositoriesHealthIndicatorUseCase, + useValue: mockRepositoriesHealthIndicatorUseCase, + }, + HealthGrpcController, + ], + }).compile(); + + healthGrpcController = + module.get(HealthGrpcController); + }); + + afterEach(async () => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(healthGrpcController).toBeDefined(); + }); + + it('should return a Serving status ', async () => { + jest.spyOn(mockRepositoriesHealthIndicatorUseCase, 'isHealthy'); + const servingStatus: { status: ServingStatus } = + await healthGrpcController.check(); + expect(servingStatus).toEqual({ + status: ServingStatus.SERVING, + }); + expect( + mockRepositoriesHealthIndicatorUseCase.isHealthy, + ).toHaveBeenCalledTimes(1); + }); + + it('should return a Not Serving status ', async () => { + jest.spyOn(mockRepositoriesHealthIndicatorUseCase, 'isHealthy'); + const servingStatus: { status: ServingStatus } = + await healthGrpcController.check(); + expect(servingStatus).toEqual({ + status: ServingStatus.NOT_SERVING, + }); + expect( + mockRepositoriesHealthIndicatorUseCase.isHealthy, + ).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/modules/health/tests/unit/health.http.controller.spec.ts b/src/modules/health/tests/unit/health.http.controller.spec.ts new file mode 100644 index 0000000..8982358 --- /dev/null +++ b/src/modules/health/tests/unit/health.http.controller.spec.ts @@ -0,0 +1,90 @@ +import { RepositoriesHealthIndicatorUseCase } from '@modules/health/core/usecases/repositories.health-indicator.usecase'; +import { HealthHttpController } from '@modules/health/interface/http-controllers/health.http.controller'; +import { HealthCheckResult, HealthCheckService } from '@nestjs/terminus'; +import { Test, TestingModule } from '@nestjs/testing'; + +const mockHealthCheckService = { + check: jest + .fn() + .mockImplementationOnce(() => ({ + status: 'ok', + info: { + repositories: { + status: 'up', + }, + }, + error: {}, + details: { + repositories: { + status: 'up', + }, + }, + })) + .mockImplementationOnce(() => ({ + status: 'error', + info: {}, + error: { + repository: + "\nInvalid `prisma.$queryRaw()` invocation:\n\n\nCan't reach database server at `v3-db`:`5432`\n\nPlease make sure your database server is running at `v3-db`:`5432`.", + }, + details: { + repository: + "\nInvalid `prisma.$queryRaw()` invocation:\n\n\nCan't reach database server at `v3-db`:`5432`\n\nPlease make sure your database server is running at `v3-db`:`5432`.", + }, + })), +}; + +const mockRepositoriesHealthIndicatorUseCase = { + isHealthy: jest.fn(), +}; + +describe('Health Http Controller', () => { + let healthHttpController: HealthHttpController; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + { + provide: HealthCheckService, + useValue: mockHealthCheckService, + }, + { + provide: RepositoriesHealthIndicatorUseCase, + useValue: mockRepositoriesHealthIndicatorUseCase, + }, + HealthHttpController, + ], + }).compile(); + + healthHttpController = + module.get(HealthHttpController); + }); + + afterEach(async () => { + jest.clearAllMocks(); + }); + + it('should be defined', () => { + expect(healthHttpController).toBeDefined(); + }); + + it('should return an HealthCheckResult with Ok status ', async () => { + jest.spyOn(mockHealthCheckService, 'check'); + jest.spyOn(mockRepositoriesHealthIndicatorUseCase, 'isHealthy'); + + const healthCheckResult: HealthCheckResult = + await healthHttpController.check(); + expect(healthCheckResult.status).toBe('ok'); + expect(mockHealthCheckService.check).toHaveBeenCalledTimes(1); + }); + + it('should return an HealthCheckResult with Error status ', async () => { + jest.spyOn(mockHealthCheckService, 'check'); + jest.spyOn(mockRepositoriesHealthIndicatorUseCase, 'isHealthy'); + + const healthCheckResult: HealthCheckResult = + await healthHttpController.check(); + expect(healthCheckResult.status).toBe('error'); + expect(mockHealthCheckService.check).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/modules/health/tests/unit/message-publisher.spec.ts b/src/modules/health/tests/unit/message-publisher.spec.ts index eec02ea..7b3e9a9 100644 --- a/src/modules/health/tests/unit/message-publisher.spec.ts +++ b/src/modules/health/tests/unit/message-publisher.spec.ts @@ -1,6 +1,6 @@ +import { MessagePublisher } from '@modules/health/infrastructure/message-publisher'; import { Test, TestingModule } from '@nestjs/testing'; -import { MessagePublisher } from '../../adapters/secondaries/message-publisher'; -import { MESSAGE_BROKER_PUBLISHER } from '../../../../app.constants'; +import { MESSAGE_BROKER_PUBLISHER } from '@src/app.constants'; const mockMessageBrokerPublisher = { publish: jest.fn().mockImplementation(), diff --git a/src/modules/health/tests/unit/repositories.health-indicator.usecase.spec.ts b/src/modules/health/tests/unit/repositories.health-indicator.usecase.spec.ts index 0353505..cf9a7fc 100644 --- a/src/modules/health/tests/unit/repositories.health-indicator.usecase.spec.ts +++ b/src/modules/health/tests/unit/repositories.health-indicator.usecase.spec.ts @@ -1,19 +1,25 @@ import { Test, TestingModule } from '@nestjs/testing'; import { HealthCheckError, HealthIndicatorResult } from '@nestjs/terminus'; -import { RepositoriesHealthIndicatorUseCase } from '../../domain/usecases/repositories.health-indicator.usecase'; -import { AdsRepository } from '../../../ad/adapters/secondaries/ads.repository'; +import { RepositoriesHealthIndicatorUseCase } from '../../core/usecases/repositories.health-indicator.usecase'; +import { AD_REPOSITORY } from '@modules/health/health.di-tokens'; +import { MESSAGE_PUBLISHER } from '@src/app.constants'; +import { DatabaseErrorException } from '@libs/exceptions'; -const mockAdsRepository = { +const mockAdRepository = { healthCheck: jest .fn() .mockImplementationOnce(() => { return Promise.resolve(true); }) .mockImplementation(() => { - throw new Error('an error occured in the repository'); + throw new DatabaseErrorException('An error occured in the database'); }), }; +const mockMessagePublisher = { + publish: jest.fn().mockImplementation(), +}; + describe('RepositoriesHealthIndicatorUseCase', () => { let repositoriesHealthIndicatorUseCase: RepositoriesHealthIndicatorUseCase; @@ -22,8 +28,12 @@ describe('RepositoriesHealthIndicatorUseCase', () => { providers: [ RepositoriesHealthIndicatorUseCase, { - provide: AdsRepository, - useValue: mockAdsRepository, + provide: AD_REPOSITORY, + useValue: mockAdRepository, + }, + { + provide: MESSAGE_PUBLISHER, + useValue: mockMessagePublisher, }, ], }).compile(); @@ -42,14 +52,15 @@ describe('RepositoriesHealthIndicatorUseCase', () => { it('should check health successfully', async () => { const healthIndicatorResult: HealthIndicatorResult = await repositoriesHealthIndicatorUseCase.isHealthy('repositories'); - expect(healthIndicatorResult['repositories'].status).toBe('up'); }); it('should throw an error if database is unavailable', async () => { + jest.spyOn(mockMessagePublisher, 'publish'); await expect( repositoriesHealthIndicatorUseCase.isHealthy('repositories'), ).rejects.toBeInstanceOf(HealthCheckError); + expect(mockMessagePublisher.publish).toHaveBeenCalledTimes(1); }); }); }); diff --git a/tsconfig.json b/tsconfig.json index adb614c..ed12947 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,6 +16,11 @@ "noImplicitAny": false, "strictBindCallApply": false, "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "noFallthroughCasesInSwitch": false, + "paths": { + "@libs/*": ["src/libs/*"], + "@modules/*": ["src/modules/*"], + "@src/*": ["src/*"] + } } }