diff --git a/.env-example b/.env-example new file mode 100644 index 0000000..17c397c --- /dev/null +++ b/.env-example @@ -0,0 +1,11 @@ +PORT=3000 +SALT=salt +DB_HOST=127.0.0.1 +DB_NAME=six-cities +DB_USERNAME=Admin +DB_PASSWORD=admin +DB_PORT=27017 +UPLOAD_DIRECTIRY=\upload +JWT_SECRET=secret +HOST=localhost +STATIC_DIRECTORY_PATH=static\ diff --git a/custom.d.ts b/custom.d.ts new file mode 100644 index 0000000..110e8bf --- /dev/null +++ b/custom.d.ts @@ -0,0 +1,7 @@ +import { TokenPayload } from './src/shared/modules/auth/helpers/types.js'; + +declare module 'express-serve-static-core' { + export interface Request { + tokenPayload: TokenPayload; + } +} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..e54d65b --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,28 @@ +services: + db: + image: mongo:4.2 + restart: always + container_name: six-cities_mongodb + environment: + MONGO_INITDB_ROOT_USERNAME: ${DB_USERNAME} + MONGO_INITDB_ROOT_PASSWORD: ${DB_PASSWORD} + ports: + - ${DB_PORT}:27017 + volumes: + - six-cities_data:/data/db + + db_ui: + image: mongo-express:latest + restart: always + container_name: six-cities_mongo_express + ports: + - 8081:8081 + environment: + ME_CONFIG_BASICAUTH_USERNAME: ${DB_USERNAME} + ME_CONFIG_BASICAUTH_PASSWORD: ${DB_PASSWORD} + ME_CONFIG_MONGODB_ADMINUSERNAME: ${DB_USERNAME} + ME_CONFIG_MONGODB_ADMINPASSWORD: ${DB_PASSWORD} + ME_CONFIG_MONGODB_URL: mongodb://${DB_USERNAME}:${DB_PASSWORD}@db:${DB_PORT}/ + +volumes: + six-cities_data: diff --git a/mocks/mock-server-data.json b/mocks/mock-server-data.json index 600d8f3..5aee338 100644 --- a/mocks/mock-server-data.json +++ b/mocks/mock-server-data.json @@ -15,10 +15,11 @@ ], "city": [ "Paris", - "Moscow", - "Kazan", - "St. Petersburg", - "Sochi" + "Cologne", + "Brussels", + "Amsterdam", + "Hamburg", + "Dusseldorf" ], "previewImage": [ "previewImage1.jpg", @@ -37,7 +38,8 @@ "type": [ "room", "apartment", - "hotel" + "hotel", + "house" ], "goods": [ "Kitchen", diff --git a/nodemon.json b/nodemon.json new file mode 100644 index 0000000..bddbd78 --- /dev/null +++ b/nodemon.json @@ -0,0 +1,7 @@ +{ + "watch": [ + "src" + ], + "ext": "ts, json", + "exec": "npm run ts ./src/main.rest.ts | pino-pretty --colorize --translateTime SYS:standard" +} diff --git a/package-lock.json b/package-lock.json index e4c2294..a930da1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,25 +8,41 @@ "name": "six-cities", "version": "7.0.0", "dependencies": { + "@typegoose/typegoose": "12.8.0", + "@types/cors": "2.8.17", "chalk": "5.3.0", + "class-transformer": "0.5.1", + "class-validator": "0.14.1", "convict": "6.2.4", "convict-format-with-validator": "6.2.0", + "cors": "2.8.5", "dayjs": "1.11.13", "dotenv": "16.4.5", + "express": "4.21.1", + "express-async-handler": "1.2.0", "got": "14.4.2", + "http-status-codes": "2.3.0", "inversify": "6.0.2", + "jose": "5.9.6", + "mime-types": "2.1.35", + "mongoose": "8.7.2", + "multer": "1.4.5-lts.1", "pino": "9.5.0", "reflect-metadata": "0.2.2" }, "devDependencies": { "@types/convict": "6.1.6", "@types/convict-format-with-validator": "6.0.5", + "@types/express": "5.0.0", + "@types/mime-types": "2.1.4", + "@types/multer": "1.4.12", "@types/node": "20.12.7", "@typescript-eslint/eslint-plugin": "6.7.0", "@typescript-eslint/parser": "6.7.0", "eslint": "8.49.0", "eslint-config-htmlacademy": "9.1.1", "json-server": "1.0.0-beta.3", + "nodemon": "3.1.7", "pino-pretty": "11.3.0", "rimraf": "5.0.1", "ts-node": "10.9.2", @@ -323,6 +339,14 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -663,6 +687,43 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "dev": true }, + "node_modules/@typegoose/typegoose": { + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/@typegoose/typegoose/-/typegoose-12.8.0.tgz", + "integrity": "sha512-YCeYYH0joT4n48WRUfofPq3KBg6OQw1zR6wB4WKflkFYf9SC4P29hf0PlmsiA+hAbubd3Qn51KmkjiUJetJmFQ==", + "dependencies": { + "lodash": "^4.17.20", + "loglevel": "^1.9.2", + "reflect-metadata": "^0.2.2", + "semver": "^7.6.3", + "tslib": "^2.7.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "mongoose": "~8.7.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/convict": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/@types/convict/-/convict-6.1.6.tgz", @@ -681,22 +742,80 @@ "@types/convict": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true + }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "20.12.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -707,12 +826,63 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "node_modules/@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==" + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.0.tgz", @@ -914,6 +1084,18 @@ "node": ">=6.5" } }, + "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==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -984,6 +1166,24 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "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, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "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==" + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -1009,6 +1209,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "node_modules/array-includes": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", @@ -1114,6 +1319,54 @@ } ] }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "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==", + "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==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1136,6 +1389,14 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", + "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -1160,6 +1421,11 @@ "ieee754": "^1.2.1" } }, + "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==" + }, "node_modules/builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", @@ -1172,6 +1438,25 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -1198,13 +1483,18 @@ } }, "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==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1260,6 +1550,21 @@ "node": ">=8" } }, + "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==" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "node_modules/clean-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", @@ -1311,6 +1616,66 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "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" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "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==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/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==" + }, + "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==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "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==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convict": { "version": "6.2.4", "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", @@ -1334,6 +1699,36 @@ "node": ">=6" } }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "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==" + }, + "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==" + }, + "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==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -1372,7 +1767,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -1424,6 +1818,22 @@ "node": ">=10" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -1440,6 +1850,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==", + "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==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -1517,12 +1944,25 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "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==" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "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", @@ -1589,6 +2029,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-escape-html": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/es-escape-html/-/es-escape-html-0.1.1.tgz", @@ -1638,6 +2097,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2029,6 +2493,14 @@ "url": "https://github.com/eta-dev/eta?sponsor=1" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -2047,6 +2519,65 @@ "node": ">=0.8.x" } }, + "node_modules/express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express-async-handler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz", + "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==" + }, + "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==", + "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==" + }, "node_modules/fast-copy": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", @@ -2146,6 +2677,36 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "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==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2249,17 +2810,49 @@ "node": ">= 18" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "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==", + "engines": { + "node": ">= 0.6" + } + }, "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.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -2289,15 +2882,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2445,7 +3042,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -2525,12 +3121,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2540,7 +3135,6 @@ "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==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2552,7 +3146,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -2575,6 +3168,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/header-range-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/header-range-parser/-/header-range-parser-1.1.3.tgz", @@ -2601,6 +3205,26 @@ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, + "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==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==" + }, "node_modules/http-status-emojis": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/http-status-emojis/-/http-status-emojis-2.2.0.tgz", @@ -2619,6 +3243,17 @@ "node": ">=10.19.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==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -2648,6 +3283,12 @@ "node": ">= 4" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2704,8 +3345,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/internal-slot": { "version": "1.0.5", @@ -2767,6 +3407,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -3012,6 +3664,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3036,6 +3693,14 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jose": { + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -3150,6 +3815,14 @@ "node": ">=4.0" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3171,6 +3844,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.12.tgz", + "integrity": "sha512-QkJn9/D7zZ1ucvT++TQSvZuSA2xAWeUytU+DiEQwbPKLyrDpvbul2AFs1CGbRAPpSCCk47aRAb5DX5mmcayp4g==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -3195,8 +3873,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -3209,6 +3886,18 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3247,24 +3936,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, "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 }, + "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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3274,6 +3972,14 @@ "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==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -3311,6 +4017,25 @@ "node": ">=16" } }, + "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==", + "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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/mimic-response": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", @@ -3347,7 +4072,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3361,6 +4085,116 @@ "node": ">=16 || 14 >=14.17" } }, + "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==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mongodb": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", + "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.7.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.2.tgz", + "integrity": "sha512-Ok4VzMds9p5G3ZSUhmvBm1GdxanbzhS29jpSn02SPj+IXEVFnIdfwAlHHXWkyNscZKlcn8GuMi68FH++jo0flg==", + "dependencies": { + "bson": "^6.7.0", + "kareem": "2.6.3", + "mongodb": "6.9.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -3373,8 +4207,24 @@ "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==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } }, "node_modules/natural-compare": { "version": "1.4.0", @@ -3386,11 +4236,107 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true, "engines": { "node": ">= 0.6" } }, + "node_modules/nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/nodemon/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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/nodemon/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/nodemon/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/nodemon/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, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -3412,6 +4358,15 @@ "semver": "bin/semver" } }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-url": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", @@ -3427,16 +4382,17 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "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==", - "dev": true, + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3537,6 +4493,17 @@ "node": ">=14.0.0" } }, + "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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3640,6 +4607,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "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", @@ -3698,6 +4673,11 @@ "node": "14 || >=16.14" } }, + "node_modules/path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -3805,6 +4785,11 @@ "node": ">= 0.6.0" } }, + "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==" + }, "node_modules/process-warning": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", @@ -3821,6 +4806,32 @@ "react-is": "^16.13.1" } }, + "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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/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==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "node_modules/pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -3835,11 +4846,24 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, "engines": { "node": ">=6" } }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3876,6 +4900,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -4194,7 +5240,6 @@ "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": [ { "type": "github", @@ -4233,35 +5278,131 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/secure-json-parse": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", + "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", + "dev": true + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, "engines": { - "node": ">=10" + "node": ">=4" } }, - "node_modules/secure-json-parse": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", - "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==", - "dev": true + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "lru-cache": "^6.0.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" }, - "bin": { - "semver": "bin/semver.js" + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4284,19 +5425,27 @@ } }, "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==", - "dev": true, + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -4309,6 +5458,18 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -4355,6 +5516,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -4395,6 +5564,14 @@ "node": ">= 10.x" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/steno": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", @@ -4407,6 +5584,14 @@ "url": "https://github.com/sponsors/typicode" } }, + "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": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -4644,6 +5829,14 @@ "node": ">=8.0" } }, + "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==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", @@ -4653,6 +5846,26 @@ "node": ">=6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "dependencies": { + "punycode": "^2.3.0" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -4711,8 +5924,7 @@ "node_modules/tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "dev": true + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "node_modules/type-check": { "version": "0.4.0", @@ -4738,6 +5950,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -4752,6 +5976,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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==" + }, "node_modules/typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -4780,11 +6009,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } }, "node_modules/uri-js": { "version": "4.4.1", @@ -4795,6 +6037,19 @@ "punycode": "^2.1.0" } }, + "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==" + }, + "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==", + "engines": { + "node": ">= 0.4.0" + } + }, "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", @@ -4819,6 +6074,34 @@ "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==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "dependencies": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4970,11 +6253,13 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "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 + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } }, "node_modules/yargs-parser": { "version": "20.2.9", @@ -5220,6 +6505,14 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -5466,6 +6759,37 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "dev": true }, + "@typegoose/typegoose": { + "version": "12.8.0", + "resolved": "https://registry.npmjs.org/@typegoose/typegoose/-/typegoose-12.8.0.tgz", + "integrity": "sha512-YCeYYH0joT4n48WRUfofPq3KBg6OQw1zR6wB4WKflkFYf9SC4P29hf0PlmsiA+hAbubd3Qn51KmkjiUJetJmFQ==", + "requires": { + "lodash": "^4.17.20", + "loglevel": "^1.9.2", + "reflect-metadata": "^0.2.2", + "semver": "^7.6.3", + "tslib": "^2.7.0" + } + }, + "@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/convict": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/@types/convict/-/convict-6.1.6.tgz", @@ -5484,22 +6808,80 @@ "@types/convict": "*" } }, + "@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz", + "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz", + "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" }, + "@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "@types/json-schema": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, + "@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "@types/mime-types": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@types/mime-types/-/mime-types-2.1.4.tgz", + "integrity": "sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==", + "dev": true + }, + "@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/node": { "version": "20.12.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.7.tgz", "integrity": "sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==", - "dev": true, "requires": { "undici-types": "~5.26.4" } @@ -5510,12 +6892,63 @@ "integrity": "sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==", "dev": true }, + "@types/qs": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz", + "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==", + "dev": true + }, + "@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, "@types/semver": { "version": "7.5.2", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", "dev": true }, + "@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "requires": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "@types/validator": { + "version": "13.12.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz", + "integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==" + }, + "@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" + }, + "@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "requires": { + "@types/webidl-conversions": "*" + } + }, "@typescript-eslint/eslint-plugin": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.0.tgz", @@ -5625,6 +7058,15 @@ "event-target-shim": "^5.0.0" } }, + "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==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, "acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -5671,6 +7113,21 @@ "color-convert": "^2.0.1" } }, + "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, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -5693,6 +7150,11 @@ "is-array-buffer": "^3.0.1" } }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "array-includes": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", @@ -5760,6 +7222,46 @@ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true }, + "binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true + }, + "body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -5779,6 +7281,11 @@ "fill-range": "^7.0.1" } }, + "bson": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.8.0.tgz", + "integrity": "sha512-iOJg8pr7wq2tg/zSlCCHMi3hMm5JTOxLTagf3zxhcenHsFp+c6uOs6K7W5UE7A4QIJGtqh/ZovFNMP4mOPJynQ==" + }, "buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -5789,12 +7296,30 @@ "ieee754": "^1.2.1" } }, + "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==" + }, "builtin-modules": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", "dev": true }, + "busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "requires": { + "streamsearch": "^1.1.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, "cacheable-lookup": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", @@ -5815,13 +7340,15 @@ } }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -5850,6 +7377,21 @@ "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", "dev": true }, + "class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "requires": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "clean-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clean-regexp/-/clean-regexp-1.0.0.tgz", @@ -5894,6 +7436,59 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "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==" + }, + "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==", + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "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==", + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, "convict": { "version": "6.2.4", "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", @@ -5911,6 +7506,30 @@ "validator": "^13.6.0" } }, + "cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "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==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -5943,7 +7562,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -5974,6 +7592,16 @@ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -5984,6 +7612,16 @@ "object-keys": "^1.1.1" } }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -6036,12 +7674,22 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "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==" + }, "emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -6102,6 +7750,19 @@ "which-typed-array": "^1.1.9" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-escape-html": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/es-escape-html/-/es-escape-html-0.1.1.tgz", @@ -6139,6 +7800,11 @@ "is-symbol": "^1.0.2" } }, + "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==" + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -6415,6 +8081,11 @@ "integrity": "sha512-e3x3FBvGzeCIHhF+zhK8FZA2vC5uFn6b4HJjegUbIWrDb4mJ7JjTGMJY9VGIbRVpmSwHopNiaJibhjIr+HfLug==", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, "event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -6427,6 +8098,64 @@ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true }, + "express": { + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.10", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "express-async-handler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/express-async-handler/-/express-async-handler-1.2.0.tgz", + "integrity": "sha512-rCSVtPXRmQSW8rmik/AIb2P0op6l7r1fMW538yyvTMltCO4xQEWMmobfrIxN2V1/mVrgxB8Az3reYF6yUZw37w==" + }, "fast-copy": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", @@ -6513,6 +8242,35 @@ "to-regex-range": "^5.0.1" } }, + "finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -6588,17 +8346,33 @@ "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==" }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, "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 }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.5", @@ -6619,14 +8393,15 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-stream": { @@ -6726,7 +8501,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -6784,25 +8558,22 @@ "dev": true }, "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "requires": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" } }, "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==", - "dev": true + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", @@ -6813,6 +8584,14 @@ "has-symbols": "^1.0.2" } }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "header-range-parser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/header-range-parser/-/header-range-parser-1.1.3.tgz", @@ -6836,6 +8615,23 @@ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" }, + "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==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==" + }, "http-status-emojis": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/http-status-emojis/-/http-status-emojis-2.2.0.tgz", @@ -6851,6 +8647,14 @@ "resolve-alpn": "^1.2.0" } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -6863,6 +8667,12 @@ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -6904,8 +8714,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "internal-slot": { "version": "1.0.5", @@ -6955,6 +8764,15 @@ "has-bigints": "^1.0.1" } }, + "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, + "requires": { + "binary-extensions": "^2.0.0" + } + }, "is-boolean-object": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", @@ -7110,6 +8928,11 @@ "call-bind": "^1.0.2" } }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -7126,6 +8949,11 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "jose": { + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==" + }, "joycon": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", @@ -7213,6 +9041,11 @@ "object.assign": "^4.1.3" } }, + "kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==" + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -7231,6 +9064,11 @@ "type-check": "~0.4.0" } }, + "libphonenumber-js": { + "version": "1.11.12", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.12.tgz", + "integrity": "sha512-QkJn9/D7zZ1ucvT++TQSvZuSA2xAWeUytU+DiEQwbPKLyrDpvbul2AFs1CGbRAPpSCCk47aRAb5DX5mmcayp4g==" + }, "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -7249,8 +9087,7 @@ "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "lodash.clonedeep": { "version": "4.5.0", @@ -7263,6 +9100,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -7286,27 +9128,38 @@ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==" }, - "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, - "requires": { - "yallist": "^4.0.0" - } - }, "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 }, + "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==" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" + }, + "merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" + }, "merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, "micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -7329,6 +9182,19 @@ "integrity": "sha512-v8yqInVjhXyqP6+Kw4fV3ZzeMRqEW6FotRsKXjRS5VMTNIuXsdRoAvklpoRgSqXm6o9VNH4/C0mgedko9DdLsQ==", "dev": true }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "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==", + "requires": { + "mime-db": "1.52.0" + } + }, "mimic-response": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", @@ -7352,8 +9218,7 @@ "minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { "version": "7.0.3", @@ -7361,6 +9226,67 @@ "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", "dev": true }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + }, + "mongodb": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.9.0.tgz", + "integrity": "sha512-UMopBVx1LmEUbW/QE0Hw18u583PEDVQmUmVzzBRH0o/xtE9DBRA5ZYLOjpLIa03i8FXjzvQECJcqoMvCXftTUA==", + "requires": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.7.0", + "mongodb-connection-string-url": "^3.0.0" + } + }, + "mongodb-connection-string-url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz", + "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==", + "requires": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^13.0.0" + } + }, + "mongoose": { + "version": "8.7.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.7.2.tgz", + "integrity": "sha512-Ok4VzMds9p5G3ZSUhmvBm1GdxanbzhS29jpSn02SPj+IXEVFnIdfwAlHHXWkyNscZKlcn8GuMi68FH++jo0flg==", + "requires": { + "bson": "^6.7.0", + "kareem": "2.6.3", + "mongodb": "6.9.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==" + }, + "mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "requires": { + "debug": "4.x" + } + }, "mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -7370,8 +9296,21 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } }, "natural-compare": { "version": "1.4.0", @@ -7382,8 +9321,76 @@ "negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "dev": true + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "nodemon": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz", + "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==", + "dev": true, + "requires": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "dependencies": { + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "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, + "requires": { + "is-glob": "^4.0.1" + } + }, + "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 + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "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, + "requires": { + "has-flag": "^3.0.0" + } + } + } }, "normalize-package-data": { "version": "2.5.0", @@ -7405,6 +9412,12 @@ } } }, + "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 + }, "normalize-url": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", @@ -7413,14 +9426,12 @@ "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==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "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==", - "dev": true + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "object-keys": { "version": "1.1.1", @@ -7488,6 +9499,14 @@ "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==" }, + "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==", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -7561,6 +9580,11 @@ "lines-and-columns": "^1.1.6" } }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -7603,6 +9627,11 @@ } } }, + "path-to-regexp": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + }, "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -7686,6 +9715,11 @@ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true }, + "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==" + }, "process-warning": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.0.tgz", @@ -7702,6 +9736,28 @@ "react-is": "^16.13.1" } }, + "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==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "dependencies": { + "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==" + } + } + }, + "pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, "pump": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz", @@ -7715,8 +9771,15 @@ "punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" + }, + "qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "requires": { + "side-channel": "^1.0.6" + } }, "queue-microtask": { "version": "1.2.3", @@ -7734,6 +9797,22 @@ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" }, + "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==" + }, + "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==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, "react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -7948,8 +10027,7 @@ "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 + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-regex": { "version": "2.1.1", @@ -7976,6 +10054,11 @@ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==" }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "secure-json-parse": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz", @@ -7983,14 +10066,91 @@ "dev": true }, "semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "lru-cache": "^6.0.0" + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" } }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8007,22 +10167,36 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, + "sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==" + }, "signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true }, + "simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, "sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", @@ -8057,6 +10231,14 @@ "dot-prop": "^9.0.0" } }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "requires": { + "memory-pager": "^1.0.2" + } + }, "spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", @@ -8094,12 +10276,22 @@ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, "steno": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", "dev": true }, + "streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" + }, "string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -8276,12 +10468,31 @@ "is-number": "^7.0.0" } }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, "totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", "dev": true }, + "touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true + }, + "tr46": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", + "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", + "requires": { + "punycode": "^2.3.0" + } + }, "ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -8313,8 +10524,7 @@ "tslib": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "dev": true + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, "type-check": { "version": "0.4.0", @@ -8331,6 +10541,15 @@ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, "typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -8342,6 +10561,11 @@ "is-typed-array": "^1.1.9" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -8360,11 +10584,21 @@ "which-boxed-primitive": "^1.0.2" } }, + "undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "uri-js": { "version": "4.4.1", @@ -8375,6 +10609,16 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "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==" + }, "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", @@ -8396,6 +10640,25 @@ "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz", "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==" }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==" + }, + "whatwg-url": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", + "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", + "requires": { + "tr46": "^4.1.1", + "webidl-conversions": "^7.0.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -8502,11 +10765,10 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "yargs-parser": { "version": "20.2.9", diff --git a/package.json b/package.json index b5d827f..00d9e2f 100644 --- a/package.json +++ b/package.json @@ -9,23 +9,29 @@ ], "main": "main.js", "scripts": { - "start:dev": "npm run ts ./src/main.rest.js | pino-pretty --colorize --translateTime SYS:standard", + "start:dev": "nodemon", "build": "npm run clean && npm run compile", "lint": "eslint src/ --ext .ts", "compile": "tsc -p tsconfig.json", "clean": "rimraf dist", "ts": "tsc --noEmit && node --no-warnings=--no-warnings=ExperimentalWarning --loader ts-node/esm", - "mock:server": "json-server ./mocks/mock-server-data.json --port 3123 --host localhost" + "mock:server": "json-server ./mocks/mock-server-data.json --port 3123 --host localhost", + "generate:dev": "npm run ts ./src/main.cli.ts -- --generate 15 ./mocks/new-data.tsv http://localhost:3123/api", + "import:dev": "npm run ts ./src/main.cli.ts -- --import ./mocks/new-data.tsv Admin admin localhost six-cities salt" }, "devDependencies": { "@types/convict": "6.1.6", "@types/convict-format-with-validator": "6.0.5", + "@types/express": "5.0.0", + "@types/mime-types": "2.1.4", + "@types/multer": "1.4.12", "@types/node": "20.12.7", "@typescript-eslint/eslint-plugin": "6.7.0", "@typescript-eslint/parser": "6.7.0", "eslint": "8.49.0", "eslint-config-htmlacademy": "9.1.1", "json-server": "1.0.0-beta.3", + "nodemon": "3.1.7", "pino-pretty": "11.3.0", "rimraf": "5.0.1", "ts-node": "10.9.2", @@ -38,13 +44,25 @@ "npm": ">=10" }, "dependencies": { + "@typegoose/typegoose": "12.8.0", + "@types/cors": "2.8.17", "chalk": "5.3.0", + "class-transformer": "0.5.1", + "class-validator": "0.14.1", "convict": "6.2.4", "convict-format-with-validator": "6.2.0", + "cors": "2.8.5", "dayjs": "1.11.13", "dotenv": "16.4.5", + "express": "4.21.1", + "express-async-handler": "1.2.0", "got": "14.4.2", + "http-status-codes": "2.3.0", "inversify": "6.0.2", + "jose": "5.9.6", + "mime-types": "2.1.35", + "mongoose": "8.7.2", + "multer": "1.4.5-lts.1", "pino": "9.5.0", "reflect-metadata": "0.2.2" } diff --git a/specification/project.spec.yml b/specification/project.spec.yml new file mode 100644 index 0000000..1b43db8 --- /dev/null +++ b/specification/project.spec.yml @@ -0,0 +1,408 @@ +openapi: 3.1.0 +info: + title: API-сервер для пет-проекта «6 городов». + description: |- + * Список ресурсов и маршрутов сервера «6 городов». + lecense: + name: MIT + url: https://opensource.org/licenses/MIT + version: 2.0.0 + + +tags: + - name: Offers + description: Действия с предложениями. + - name: Favorite + description: Действия с избранными предложениями. + - name: Premium + description: Действия с премиальными предложениями. + - name: Comments + description: Действия с комментариями. + - name: Users + description: Действия с пользователем. + + +paths: + /offers/offer: + post: + tags: + - Offers + summary: Добавить новое предложение по аренде. + description: Добавляет новое предложение по аренде. + + requestBody: + description: Информация для создания нового предложения. + content: + application/json: + schema: + $ref: '#/components/schema/createOffer' + required: true + + response: + "201": + description: Предложение добавлено. Объект пользователя. + content: + application/json: + schema: + $ref: '#/components/schema/offer' + + "409": + description: Не удалось создать предложение. + + get: + tags: + - Offers + summary: Получить список предложений по аренде. + description: Возвращает не более 60 предложений по аренде. + + response: + "200": + description: Список предложений по аренде. + content: + application/json: + schema: + $ref: '#/components/schema/previewOffer' + + /offers/offer/{offerId}: + get: + tags: + - Offers + summary: Получить подробное предложение по аренде. + description: Возвращает подробное предложение по аренде. + + response: + description: Подробное предложение по аренде. + content: + application/json: + schema: + $ref: '#/components/schema/offer' + + + put: + tags: + - Offers + summary: Редактировать предложение по аренде. + description: Редактирует предложение по аренде. + + delete: + tags: + - Offers + summary: Удалить предложение по аренде. + description: Удаляет предложение по аренде. + + /offers/favorite: + get: + tags: + - Favorite + summary: Получить список избранных предложений. + description: Возвращается список избранных предложений. + + response: + "200": + description: Список избранных предложений по аренде. + content: + application/json: + schema: + $ref: '#/components/schema/previewOffer' + + /favorite/{offerId}/{status}: + post: + tags: + - Favorite + summary: Изменяет статус избранного предложения. + description: Статус избранного предложения. Может быть 1 или 0, где 1 добавляет предложение в избранное, а 0 удаляет. + + /offers/premium: + get: + tags: + - Premium + summary: Получить список премиальных предложений. + description: Возвращает не более 3 предложений с флагом «Premium». + + response: + "200": + description: Список премиальных предложений по аренде. + content: + application/json: + schema: + $ref: '#/components/schema/previewOffer' + + + /comments/{offerId}: + get: + tags: + - Comments + summary: Получить список комментариев к предложению. + description: Возвращает список из 50 (или меньше) последних комментариев. + + post: + tags: + - Comments + summary: Добавить комментарий к предложению. + description: Добавляет новый комментарий. + + /users/register: + post: + tags: + - Users + summary: Регистрация пользователя + description: Регистрирует нового пользователя. + + requestBody: + description: Информация для создания нового пользователя. + content: + application/json: + schema: + $ref: '#/components/schema/createUser' + required: true + + response: + "201": + description: Пользователь зарегистрирован. Объект пользователя. + content: + application/json: + schema: + $ref: '#/components/schema/users' + + "409": + description: Пользователь с таким email уже существует. + + /users/login: + post: + tags: + - Users + summary: Аутентификация пользователя. + description: Авторизует пользователя и позволяет войти в закрытую часть приложения. + + requestBody: + description: Аутентификация пользователя. + content: + application/json: + schema: + type: object + properties: + email: + type: string + example: Oliver.conner@gmail.com + + password: + type: string + example: 123qwe + required: true + + response: + "200": + description: Пользователь авторизован. + content: + application/json: + schema: + $ref: '#/components/schema/users' + + "401": + description: Неверный логин или пароль. + + + get: + tags: + - Users + summary: Проверка статуса пользователя. + description: Определяет статус пользователя. + + response: + "200": + description: Статус авторизации. + content: + application/json: + schema: + $ref: '#/components/schema/users' + + "400": + description: Ошибка авторизации. + + + /users/logout: + delete: + tags: + - Users + summary: Завершение сеанса работы. + description: Выходит из закрытой части приложения. + + response: + "204": + description: Сеанс завершен. + +components: + schema: + createOffer: + type: object + + properties: + title: + type: string + example: Beautiful & luxurious studio at great location + type: + type: string + example: apartment + price: + type: number + example: 560 + city: + type: string + example: Paris + isFavorite: + type: boolean + example: false + isPremium: + type: boolean + example: false + rating: + type: number + example: 4 + previewImage: + type: string + example: "https://url-to-image/image.png" + description: + type: string + example: A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam + bedrooms: + type: number + example: 3 + goods: + type: [string] + example: ["Heating"] + images: + type: [string] + example: ["https://url-to-image/image.png"] + maxAdults: + type: number + example: 2 + + offer: + type: object + + properties: + title: + type: string + example: Beautiful & luxurious studio at great location + type: + type: string + example: apartment + price: + type: number + example: 560 + city: + type: string + example: Paris + isFavorite: + type: boolean + example: false + isPremium: + type: boolean + example: false + rating: + type: number + example: 4 + previewImage: + type: string + example: "https://url-to-image/image.png" + description: + type: string + example: A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam + bedrooms: + type: number + example: 3 + goods: + type: [string] + example: ["Heating"] + images: + type: [string] + example: ["https://url-to-image/image.png"] + maxAdults: + type: number + example: 2 + host: + properties: + name: + type: string + example: Oliver Conner + avatarUrl: + type: string + example: https://url-to-image/image.png + isPro: + type: boolean + example: false + + previewOffer: + type: object + + properties: + title: + type: string + example: Beautiful & luxurious studio at great location + type: + type: string + example: apartment + price: + type: number + example: 560 + city: + type: string + example: Paris + isFavorite: + type: boolean + example: true + isPremium: + type: boolean + example: true + rating: + type: number + example: 4 + previewImage: + type: string + example: "https://url-to-image/image.png" + + createUser: + type: object + + properties: + name: + type: string + example: Roman + + avatarUrl: + type: string + example: https://url-to-image/image.png + + email: + type: string + example: login@gmail.com + + password: + type: string + example: 123qwe + + users: + type: object + + properties: + name: + type: string + example: Oliver Conner + + avatarUrl: + type: string + example: https://url-to-image/image.png + + isPro: + type: boolean + example: false + + email: + type: string + example: Oliver.conner@gmail.com + + token: + type: string + example: T2xpdmVyLmNvbm5lckBnbWFpbC5jb20= + + diff --git a/src/cli/cli.application.ts b/src/cli/cli.application.ts index 781251a..5b87a61 100644 --- a/src/cli/cli.application.ts +++ b/src/cli/cli.application.ts @@ -1,5 +1,4 @@ -import { CommandParser } from './command-parser.js'; -import { Command } from './commands/command.interface.js'; +import { CommandParser, Command} from './index.js'; type CommandCollection = Record diff --git a/src/cli/commands/command.constant.ts b/src/cli/commands/command.constant.ts new file mode 100644 index 0000000..2f2f682 --- /dev/null +++ b/src/cli/commands/command.constant.ts @@ -0,0 +1,2 @@ +export const DEFAULT_DB_PORT = '27017'; +export const DEFAULT_USER_PASSWORD = '123qwe'; diff --git a/src/cli/commands/generate.command.ts b/src/cli/commands/generate.command.ts index 0751480..4cb42c4 100644 --- a/src/cli/commands/generate.command.ts +++ b/src/cli/commands/generate.command.ts @@ -1,8 +1,8 @@ import got from 'got'; -import { Command } from './command.interface.js'; +import { Command } from './../index.js'; import { MockServerData } from '../../shared/types/index.js'; -import { TSVOfferGenerator } from '../../shared/libs/offer-generator/tsv-offer-generate.js'; -import { TSVFileWriter } from '../../shared/libs/file-writer/tsv-file-wtiter.js'; +import { TSVOfferGenerator } from '../../shared/libs/offer-generator/index.js'; +import { TSVFileWriter } from '../../shared/libs/file-writer/index.js'; export class GenerateCommand implements Command { private initialData: MockServerData; @@ -42,8 +42,6 @@ export class GenerateCommand implements Command { if (error instanceof Error) { console.error('error'); } - } finally { - console.log(count, filepath, url); } } } diff --git a/src/cli/commands/help.command.ts b/src/cli/commands/help.command.ts index b0a6d42..cf242aa 100644 --- a/src/cli/commands/help.command.ts +++ b/src/cli/commands/help.command.ts @@ -1,4 +1,4 @@ -import { Command } from './command.interface.js'; +import { Command } from './../index.js'; export class HelpCommand implements Command { diff --git a/src/cli/commands/import.command.ts b/src/cli/commands/import.command.ts index 109c389..f5c3d75 100644 --- a/src/cli/commands/import.command.ts +++ b/src/cli/commands/import.command.ts @@ -1,22 +1,72 @@ -import { TSVFileReader } from '../../shared/libs/file-reader/tsv-file-reader.js'; -import { Offer } from '../../shared/types/offer-type.js'; -import { Command } from './command.interface.js'; +import { getMongoURI } from '../../shared/helpers/index.js'; +import { DatabaseClient, MongoDatabaseClient } from '../../shared/libs/database-client/index.js'; +import { TSVFileReader } from '../../shared/libs/file-reader/index.js'; +import { Logger, ConsoleLogger} from '../../shared/libs/logger/index.js'; +import { OfferModel, OfferService, DefaultOfferService } from '../../shared/modules/offer/index.js'; +import { UserModel, UserService, DefaultUserService} from '../../shared/modules/user/index.js'; +import { Offer } from '../../shared/types/index.js'; +import { Command, DEFAULT_DB_PORT, DEFAULT_USER_PASSWORD } from './../index.js'; export class ImportCommand implements Command { + private userService: UserService; + private offerService: OfferService; + private databaseClient: DatabaseClient; + private logger: Logger; + private salt: string; + + constructor() { + this.onImportedOffer = this.onImportedOffer.bind(this); + this.onCompleteImport = this.onCompleteImport.bind(this); + + this.logger = new ConsoleLogger(); + this.offerService = new DefaultOfferService(this.logger, OfferModel); + this.userService = new DefaultUserService(this.logger, UserModel); + this.databaseClient = new MongoDatabaseClient(this.logger); + } + public getName(): string { return '--import'; } - private onImportedOffer(offer: Offer): void { - console.info(offer); + private async onImportedOffer(offer: Offer, resolve: () => void) { + await this.saveOffer(offer); + resolve(); + } + + private async saveOffer(offer: Offer) { + const user = await this.userService.findOrCreate({ + ...offer.host, + password: DEFAULT_USER_PASSWORD + }, this.salt); + + await this.offerService.create({ + title: offer.title, + description: offer.description, + postDate: offer.postDate, + city: offer.city, + previewImage: offer.previewImage, + isPremium: offer.isPremium, + rating: offer.rating, + type: offer.type, + bedrooms: offer.bedrooms, + maxAdults: offer.maxAdults, + price:offer.price, + goods: offer.goods, + host: user.id + }); } private onCompleteImport(count: number) { console.info(`${count} rows imported`); + this.databaseClient.disconnect(); } - public async execute(...parameters: string[]): Promise { - const [filename] = parameters; + public async execute(filename: string, login: string, password: string,host: string, dbname: string, salt: string): Promise { + const uri = getMongoURI(login, password, host, DEFAULT_DB_PORT, dbname); + this.salt = salt; + + await this.databaseClient.connect(uri); + const fileReader = new TSVFileReader(filename.trim()); fileReader.on('line', this.onImportedOffer); diff --git a/src/cli/commands/version.command.ts b/src/cli/commands/version.command.ts index 6164946..75235a8 100644 --- a/src/cli/commands/version.command.ts +++ b/src/cli/commands/version.command.ts @@ -1,4 +1,4 @@ -import { Command } from './command.interface.js'; +import { Command } from './../index.js'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; diff --git a/src/cli/index.ts b/src/cli/index.ts index fddeb4e..b03864f 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -4,3 +4,5 @@ export { HelpCommand } from './commands/help.command.js'; export { VersionCommand } from './commands/version.command.js'; export { ImportCommand } from './commands/import.command.js'; export { GenerateCommand } from './commands/generate.command.js'; +export { Command } from './commands/command.interface.js'; +export { DEFAULT_DB_PORT, DEFAULT_USER_PASSWORD } from './commands/command.constant.js'; diff --git a/src/main.cli.ts b/src/main.cli.ts index 62f7e88..2a31dee 100644 --- a/src/main.cli.ts +++ b/src/main.cli.ts @@ -1,3 +1,4 @@ +import 'reflect-metadata'; import { CLIApplication, HelpCommand, VersionCommand, ImportCommand, GenerateCommand} from './cli/index.js'; const bootstrap = () => { diff --git a/src/main.rest.ts b/src/main.rest.ts index 0318f50..737bb73 100644 --- a/src/main.rest.ts +++ b/src/main.rest.ts @@ -1,18 +1,22 @@ import 'reflect-metadata'; -import { RestApplication } from './rest/index.js'; -import { Config, RestConfig, RestSchema } from './shared/libs/config/index.js'; -import { Logger, PinoLogger} from './shared/libs/logger/index.js'; +import { RestApplication, createReasApplicationContainer} from './rest/index.js'; import { Container } from 'inversify'; import { Component } from './shared/types/index.js'; +import { createUserContainer } from './shared/modules/user/index.js'; +import { createOfferContainer } from './shared/modules/offer/index.js'; +import { createCommentContainer } from './shared/modules/comment/index.js'; +import { createAuthContainer } from './shared/modules/auth/index.js'; async function bootstrap() { - const container = new Container(); + const appContainer = Container.merge( + createReasApplicationContainer(), + createUserContainer(), + createOfferContainer(), + createCommentContainer(), + createAuthContainer() + ); - container.bind(Component.RestApplication).to(RestApplication).inSingletonScope(); - container.bind(Component.Logger).to(PinoLogger).inSingletonScope(); - container.bind>(Component.Config).to(RestConfig).inSingletonScope(); - - const application = container.get(Component.RestApplication); + const application = appContainer.get(Component.RestApplication); await application.init(); } diff --git a/src/main.ts b/src/main.ts index e69de29..8b13789 100644 --- a/src/main.ts +++ b/src/main.ts @@ -0,0 +1 @@ + diff --git a/src/rest/index.ts b/src/rest/index.ts index 5e51a1d..d0ddcfd 100644 --- a/src/rest/index.ts +++ b/src/rest/index.ts @@ -1 +1,3 @@ export { RestApplication } from './rest.application.js'; +export { createReasApplicationContainer } from './rest.container.js'; +export { STATIC_FILES_ROUTE, STATIC_UPLOAD_ROUTE } from './rest.constant.js'; diff --git a/src/rest/rest.application.ts b/src/rest/rest.application.ts index 860c4cd..b9d584c 100644 --- a/src/rest/rest.application.ts +++ b/src/rest/rest.application.ts @@ -2,17 +2,102 @@ import { inject, injectable } from 'inversify'; import { Config, RestSchema } from '../shared/libs/config/index.js'; import { Logger } from '../shared/libs/logger/index.js'; import { Component } from '../shared/types/index.js'; - +import { DatabaseClient } from '../shared/libs/database-client/index.js'; +import express, { Express } from 'express'; +import { Controller, ExceptionFilter, ParseTokenMiddleware } from '../shared/libs/rest/index.js'; +import { getFullServerPath, getMongoURI } from '../shared/helpers/index.js'; +import { STATIC_FILES_ROUTE, STATIC_UPLOAD_ROUTE } from './index.js'; +import cors from 'cors'; @injectable() export class RestApplication { + private readonly server: Express; + constructor( @inject(Component.Logger) private readonly logger: Logger, - @inject(Component.Config) private readonly config: Config - ) {} + @inject(Component.Config) private readonly config: Config, + @inject(Component.DatabaseClient) private readonly databaseClient: DatabaseClient, + @inject(Component.UserController) private readonly userController: Controller, + @inject(Component.OfferController) private readonly offerController: Controller, + @inject(Component.CommentController) private readonly commentController: Controller, + @inject(Component.ExceptionFilter) private readonly appExceptionFilter: ExceptionFilter, + @inject(Component.AuthExceptionFilter) private readonly authExceptionFilter: ExceptionFilter, + @inject(Component.HttpExceptionFilter) private readonly httpExceptionFilter: ExceptionFilter, + @inject(Component.ValidationExceptionFilter) private readonly validationExceptionFilter: ExceptionFilter, + ) { + this.server = express(); + } + + private async initDb() { + + const mongoUri = getMongoURI( + this.config.get('DB_USERNAME'), + this.config.get('DB_PASSWORD'), + this.config.get('DB_HOST'), + this.config.get('DB_PORT'), + this.config.get('DB_NAME'), + ); + + return this.databaseClient.connect(mongoUri); + } + + private async initServer() { + const port = this.config.get('PORT'); + this.server.listen(port); + } + + private async initControllers() { + this.server.use('/users', this.userController.router); + this.server.use('/offers', this.offerController.router); + this.server.use('/comments', this.commentController.router); + } + + private async initMiddleware() { + const authenticateMiddleware = new ParseTokenMiddleware(this.config.get('JWT_SECRET')); + + this.server.use(express.json()); + this.server.use( + STATIC_UPLOAD_ROUTE, + express.static(this.config.get('STATIC_DIRECTORY_PATH')) + ); + this.server.use( + STATIC_FILES_ROUTE, + express.static(this.config.get('UPLOAD_DIRECTIRY')) + ); + this.server.use(authenticateMiddleware.execute.bind(authenticateMiddleware)); + this.server.use(cors()); + } + + private async initExceptionFilter() { + this.server.use(this.authExceptionFilter.catch.bind(this.authExceptionFilter)); + this.server.use(this.validationExceptionFilter.catch.bind(this.validationExceptionFilter)); + this.server.use(this.httpExceptionFilter.catch.bind(this.httpExceptionFilter)); + this.server.use(this.appExceptionFilter.catch.bind(this.appExceptionFilter)); + + } public async init() { this.logger.info('Application initiazation'); this.logger.info(`Get value from env $PORT: ${this.config.get('PORT')}`); + + this.logger.info('Init database...'); + await this.initDb(); + this.logger.info('init database completed'); + + this.logger.info('Init app-level midlleware'); + await this.initMiddleware(); + this.logger.info('App-level middleware initiazation completed'); + + this.logger.info('Init controllers...'); + await this.initControllers(); + this.logger.info('Controller initiazation completed'); + + this.logger.info('Init exception filters'); + await this.initExceptionFilter(); + this.logger.info('Exception filters initialization compleated.'); + + this.logger.info('Try to init server...'); + await this.initServer(); + this.logger.info(`Server started on ${getFullServerPath(this.config.get('HOST'), this.config.get('PORT'))}`); } } diff --git a/src/rest/rest.constant.ts b/src/rest/rest.constant.ts new file mode 100644 index 0000000..77ff89d --- /dev/null +++ b/src/rest/rest.constant.ts @@ -0,0 +1,2 @@ +export const STATIC_UPLOAD_ROUTE = '/upload'; +export const STATIC_FILES_ROUTE = '/static'; diff --git a/src/rest/rest.container.ts b/src/rest/rest.container.ts new file mode 100644 index 0000000..d938f01 --- /dev/null +++ b/src/rest/rest.container.ts @@ -0,0 +1,22 @@ +import { RestApplication } from './index.js'; +import { Config, RestConfig, RestSchema } from './../shared/libs/config/index.js'; +import { Logger, PinoLogger} from './../shared/libs/logger/index.js'; +import { Container } from 'inversify'; +import { Component } from './../shared/types/index.js'; +import { DatabaseClient, MongoDatabaseClient } from './../shared/libs/database-client/index.js'; +import { AppExceptionFilter, ExceptionFilter, ValodationExceptionFilter, HttpErrorExceptionFilter, PathTransformer } from '../shared/libs/rest/index.js'; + +export function createReasApplicationContainer() { + const container = new Container(); + + container.bind(Component.RestApplication).to(RestApplication).inSingletonScope(); + container.bind(Component.Logger).to(PinoLogger).inSingletonScope(); + container.bind>(Component.Config).to(RestConfig).inSingletonScope(); + container.bind(Component.DatabaseClient).to(MongoDatabaseClient).inSingletonScope(); + container.bind(Component.ExceptionFilter).to(AppExceptionFilter).inSingletonScope(); + container.bind(Component.HttpExceptionFilter).to(HttpErrorExceptionFilter).inSingletonScope(); + container.bind(Component.ValidationExceptionFilter).to(ValodationExceptionFilter).inSingletonScope(); + container.bind(Component.PathTransformer).to(PathTransformer).inSingletonScope(); + + return container; +} diff --git a/src/shared/helpers/const.ts b/src/shared/helpers/const.ts index ba19fce..27a64d7 100644 --- a/src/shared/helpers/const.ts +++ b/src/shared/helpers/const.ts @@ -1,3 +1,5 @@ +import { City } from '../types/index.js'; + export enum WeekDay { First = 1, Last = 7 @@ -13,3 +15,50 @@ export enum Price { Mix = 3500 } +export enum Retry { + Count = 5, + Timeout = 1000 +} + +export const location: City = { + Paris: { + name: 'Paris', + latitude: 48.85661, + longitude: 2.351499 + }, + Cologne: { + name: 'Cologne', + latitude: 50.938361, + longitude: 6.959974 + }, + Brussels: { + name: 'Brussels', + latitude: 50.846557, + longitude: 4.351697 + }, + Amsterdam: { + name: 'Amsterdam', + latitude: 52.370216, + longitude: 4.895168 + }, + Hamburg: { + name: 'Hamburg', + latitude: 53.550341, + longitude: 10.000654 + }, + Dusseldorf:{ + name: 'Dusseldorf', + latitude: 51.225402, + longitude: 6.776314 + }, +} as const; + +export const DEFAULT_AVATAR_FILE_NAME = 'default-avatar.jpg'; +export const DEFAULT_OFFER_IMAGES = [ + 'static-image-1.jpg', + 'static-image-2.jpg', + 'static-image-3.jpg', + 'static-image-4.jpg', + 'static-image-5.jpg', + 'static-image-6.jpg' +]; diff --git a/src/shared/helpers/datadase.ts b/src/shared/helpers/datadase.ts new file mode 100644 index 0000000..89d4d4d --- /dev/null +++ b/src/shared/helpers/datadase.ts @@ -0,0 +1,9 @@ +export function getMongoURI( + username: string, + password: string, + host: string, + port: string, + databaseName: string +): string { + return `mongodb://${username}:${password}@${host}:${port}/${databaseName}?authSource=admin`; +} diff --git a/src/shared/helpers/functions.ts b/src/shared/helpers/functions.ts index c971d91..35f7580 100644 --- a/src/shared/helpers/functions.ts +++ b/src/shared/helpers/functions.ts @@ -1,3 +1,8 @@ +import { ClassConstructor, plainToInstance } from 'class-transformer'; +import { ValidationError } from 'class-validator'; +import { ValidationErrorField } from '../libs/rest/types/validation-error-field.js'; +import { ApplicationError } from '../libs/rest/types/application-error.enum.js'; + export function getRandomNumber(min: number, max: number, numAfterDigit: number = 0) { return +((Math.random() * (max - min)) + min).toFixed(numAfterDigit); } @@ -11,3 +16,23 @@ export function getRandomItems(items: T[]):T[] { export function getRandomElement(items: T[]):T { return items[getRandomNumber(0, items.length - 1)]; } + +export function fillDTO(someDto: ClassConstructor, plainObjaect: V) { + return plainToInstance(someDto, plainObjaect, { excludeExtraneousValues: true}); +} + +export function createErrorObject(errorType: ApplicationError, error: string, details: ValidationErrorField[] = []) { + return { errorType, error, details }; +} + +export function reduceValidationErrors(errors: ValidationError[]): ValidationErrorField[] { + return errors.map(({property, value, constraints}) => ({ + property, + value, + messages: constraints ? Object.values(constraints) : [] + })); +} + +export function getFullServerPath(host: string, port: number) { + return `http://${host}:${port}`; +} diff --git a/src/shared/helpers/hash.ts b/src/shared/helpers/hash.ts new file mode 100644 index 0000000..cff435a --- /dev/null +++ b/src/shared/helpers/hash.ts @@ -0,0 +1,6 @@ +import * as crypto from 'node:crypto'; + +export function createSHA256(line: string, salt: string): string { + const shaHasher = crypto.createHmac('sha256', salt); + return shaHasher.update(line).digest('hex'); +} diff --git a/src/shared/helpers/index.ts b/src/shared/helpers/index.ts index 2f8aee9..339dd28 100644 --- a/src/shared/helpers/index.ts +++ b/src/shared/helpers/index.ts @@ -1 +1,4 @@ -export { getRandomElement, getRandomNumber, getRandomItems } from './functions.js'; +export { getRandomElement, getRandomNumber, getRandomItems, fillDTO, createErrorObject, reduceValidationErrors, getFullServerPath } from './functions.js'; +export { getMongoURI } from './datadase.js'; +export { createSHA256 } from './hash.js'; +export { WeekDay, Attributes, Price, Retry, location, DEFAULT_AVATAR_FILE_NAME} from './const.js'; diff --git a/src/shared/libs/config/rest.schema.ts b/src/shared/libs/config/rest.schema.ts index 64bb625..f30fca5 100644 --- a/src/shared/libs/config/rest.schema.ts +++ b/src/shared/libs/config/rest.schema.ts @@ -6,7 +6,15 @@ convict.addFormats(validator); export type RestSchema = { PORT: number, SALT: string, - HOST_DB: string, + DB_HOST: string, + DB_NAME: string, + DB_USERNAME: string, + DB_PASSWORD: string, + DB_PORT: string, + UPLOAD_DIRECTIRY: string, + JWT_SECRET: string, + HOST: string + STATIC_DIRECTORY_PATH:string } export const configRestSchema = convict({ @@ -22,10 +30,58 @@ export const configRestSchema = convict({ env: 'SALT', default: null }, - HOST_DB: { + DB_HOST: { doc: 'IP address of the database server (MongoDB)', format: 'ipaddress', env: 'HOST_DB', default: '127.0.0.1' - } + }, + DB_NAME: { + doc: 'Database name (MongoDB)', + format: String, + env: 'DB_NAME', + default: 'six-cities' + }, + DB_USERNAME: { + doc: 'Username to connect to the database', + format: String, + env: 'DB_USERNAME', + default: null + }, + DB_PASSWORD: { + doc: 'Password to connect to the database', + format: String, + env: 'DB_PASSWORD', + default: null + }, + DB_PORT: { + doc: 'Port to connect to the database (MongoDB)', + format: 'port', + env: 'DB_PORT', + default: '27017' + }, + UPLOAD_DIRECTIRY: { + doc: 'Directory for upload files', + format: String, + env: 'UPLOAD_DIRECTIRY', + default: null + }, + JWT_SECRET: { + doc: 'Secret for sign JSON Web Token', + format: String, + env: 'JWT_SECRET', + default: null + }, + HOST: { + doc: 'Host where started service', + format: String, + env: 'HOST', + default: 'localhost' + }, + STATIC_DIRECTORY_PATH: { + doc: 'Path to directory with static resources', + format: String, + env: 'STATIC_DIRECTORY_PATH', + default: 'static' + }, }); diff --git a/src/shared/libs/database-client/database-client.interface.ts b/src/shared/libs/database-client/database-client.interface.ts new file mode 100644 index 0000000..db39976 --- /dev/null +++ b/src/shared/libs/database-client/database-client.interface.ts @@ -0,0 +1,4 @@ +export interface DatabaseClient { + connect(uri: string): Promise; + disconnect(): Promise; +} diff --git a/src/shared/libs/database-client/index.ts b/src/shared/libs/database-client/index.ts new file mode 100644 index 0000000..8961709 --- /dev/null +++ b/src/shared/libs/database-client/index.ts @@ -0,0 +1,2 @@ +export { DatabaseClient } from './database-client.interface.js'; +export { MongoDatabaseClient } from './mongo.database-client.js'; diff --git a/src/shared/libs/database-client/mongo.database-client.ts b/src/shared/libs/database-client/mongo.database-client.ts new file mode 100644 index 0000000..fb6d388 --- /dev/null +++ b/src/shared/libs/database-client/mongo.database-client.ts @@ -0,0 +1,59 @@ +import * as Mongoose from 'mongoose'; + +import { inject, injectable } from 'inversify'; +import { DatabaseClient } from './index.js'; +import { Component } from '../../types/index.js'; +import { Logger } from '../logger/index.js'; +import { Retry } from '../../helpers/index.js'; +import { setTimeout } from 'node:timers/promises'; + +@injectable() +export class MongoDatabaseClient implements DatabaseClient { + + private isConneted: boolean; + private mongoose: typeof Mongoose; + + constructor( + @inject(Component.Logger) private readonly logger: Logger + ) { + this.isConneted = false; + } + + public isConnectedToDatabase() { + return this.isConneted; + } + + public async connect(uri: string): Promise { + + if (this.isConnectedToDatabase()) { + throw new Error('MongoDB client already connected'); + } + + this.logger.info('Trying to connect to MongoDB...'); + + + let attempt = 0; + while (attempt < Retry.Count) { + try { + this.mongoose = await Mongoose.connect(uri); + this.isConneted = true; + this.logger.info('Database connection established.'); + return; + } catch (error) { + attempt++; + this.logger.error(`Failed to connect to the database. Attempt ${attempt}`, error as Error); + await setTimeout(Retry.Timeout); + } + } + } + + public async disconnect(): Promise { + if (!this.isConnectedToDatabase()) { + throw new Error('Not connected to the database'); + } + + await this.mongoose.disconnect?.(); + this.isConneted = false; + this.logger.info('Database connection closed'); + } +} diff --git a/src/shared/libs/file-reader/tsv-file-reader.ts b/src/shared/libs/file-reader/tsv-file-reader.ts index 87c0d1c..32b6146 100644 --- a/src/shared/libs/file-reader/tsv-file-reader.ts +++ b/src/shared/libs/file-reader/tsv-file-reader.ts @@ -1,8 +1,9 @@ import EventEmitter from 'node:events'; import { createReadStream } from 'node:fs'; - import { FileReader } from './index.js'; -import { Offer, TypeUser } from '../../types/index.js'; +import { Offer } from '../../types/index.js'; +import { TypeOffer } from '../../types/index.js'; +import { location } from '../../helpers/index.js'; export class TSVFileReader extends EventEmitter implements FileReader { private CHUNK_SIZE = 16384; @@ -32,7 +33,7 @@ export class TSVFileReader extends EventEmitter implements FileReader { email, avatarUser, password, - typeUser, + isPro, ] = line.split('\t'); return { @@ -45,12 +46,14 @@ export class TSVFileReader extends EventEmitter implements FileReader { isFavorite: this.parseBoolean(isFavorite), isPremium: this.parseBoolean(isPremium), rating: this.parseStringToNumber(rating), - type, + type: TypeOffer[type as 'apartment' | 'house' | 'room' | 'hotel'], bedrooms: this.parseStringToNumber(bedrooms), maxAdults: this.parseStringToNumber(maxAdults), price: this.parseStringToNumber(price), goods: this.parseGoods(goods), - host: this.parseHost(name, email, avatarUser, password, typeUser) + host: this.parseHost(name, email, avatarUser, password, isPro), + location: location[city], + }; } @@ -65,16 +68,17 @@ export class TSVFileReader extends EventEmitter implements FileReader { return Number.parseInt(itemString, 10); } - private parseGoods(goods: string): {good: string}[] { - return goods.split(',').map((good) => ({good})); + private parseGoods(goods: string): string[] { + return goods.split(';').map((good) => (good)); } - private parseImages(images: string): {img: string}[] { - return images.split(',').map((img) => ({img})); + private parseImages(images: string): string[] { + return images.split(';').map((img) => (img)); } - private parseHost(name: string, email: string, avatarUser: string, password: string, typeUser: string) { - return {name, email, avatarUser, password, typeUser:TypeUser[typeUser as 'Pro' | 'Usual']}; + private parseHost(name: string, email: string, avatarUser: string, password: string, status: string) { + const isPro = status === 'Pro'; + return {name, email, avatarUser, password, isPro}; } public async read(): Promise { @@ -96,7 +100,11 @@ export class TSVFileReader extends EventEmitter implements FileReader { importedRowCouint++; const parsedOffer = this.parseLineToOffer(completeRow); - this.emit('line', parsedOffer); + + await new Promise((resolve) => { + this.emit('line', parsedOffer, resolve); + }); + } } diff --git a/src/shared/libs/file-writer/index.ts b/src/shared/libs/file-writer/index.ts index 22c2b5c..4e3dcfa 100644 --- a/src/shared/libs/file-writer/index.ts +++ b/src/shared/libs/file-writer/index.ts @@ -1 +1,2 @@ export { FileWriter } from './file-writer.interface.js'; +export { TSVFileWriter } from './tsv-file-wtiter.js'; diff --git a/src/shared/libs/logger/console.logger.ts b/src/shared/libs/logger/console.logger.ts new file mode 100644 index 0000000..9cbfa96 --- /dev/null +++ b/src/shared/libs/logger/console.logger.ts @@ -0,0 +1,20 @@ +import { Logger } from './index.js'; + +export class ConsoleLogger implements Logger { + public debug(message: string, ...args: unknown[]): void { + console.debug(message, ...args); + } + + public error(message: string, error: Error, ...args: unknown[]): void { + console.error(message, ...args); + console.error(`Error message: ${error}`); + } + + public info(message: string, ...args: unknown[]): void { + console.info(message, ...args); + } + + public warn(message: string, ...args: unknown[]): void { + console.warn(message, ...args); + } +} diff --git a/src/shared/libs/logger/index.ts b/src/shared/libs/logger/index.ts index 2382413..35fcf4f 100644 --- a/src/shared/libs/logger/index.ts +++ b/src/shared/libs/logger/index.ts @@ -1,2 +1,3 @@ export { Logger} from './logger.interface.js'; export { PinoLogger } from './pino.logger.js'; +export { ConsoleLogger } from './console.logger.js'; diff --git a/src/shared/libs/offer-generator/index.ts b/src/shared/libs/offer-generator/index.ts index dc94ca3..782e85a 100644 --- a/src/shared/libs/offer-generator/index.ts +++ b/src/shared/libs/offer-generator/index.ts @@ -1 +1,2 @@ export { OfferGenerator } from './offer-generator.interface.js'; +export { TSVOfferGenerator } from './tsv-offer-generate.js'; diff --git a/src/shared/libs/offer-generator/tsv-offer-generate.ts b/src/shared/libs/offer-generator/tsv-offer-generate.ts index 6e1101b..20af663 100644 --- a/src/shared/libs/offer-generator/tsv-offer-generate.ts +++ b/src/shared/libs/offer-generator/tsv-offer-generate.ts @@ -2,7 +2,7 @@ import dayjs from 'dayjs'; import { getRandomElement, getRandomItems, getRandomNumber } from '../../helpers/index.js'; import { MockServerData } from '../../types/index.js'; import { OfferGenerator } from './index.js'; -import { Attributes, Price, WeekDay } from '../../helpers/const.js'; +import { Attributes, location, Price, WeekDay } from '../../helpers/index.js'; export class TSVOfferGenerator implements OfferGenerator { constructor(private readonly mockData: MockServerData) {} @@ -28,6 +28,7 @@ export class TSVOfferGenerator implements OfferGenerator { const password = getRandomElement(this.mockData.password); const typeUser = getRandomElement(this.mockData.typeUser); + return [ title, description, postDate, city, previewImage, images, @@ -35,7 +36,7 @@ export class TSVOfferGenerator implements OfferGenerator { type, bedrooms, maxAdults, price, goods, name, email, avatarUser, password, - typeUser + typeUser, location ].join('\t'); } } diff --git a/src/shared/libs/rest/controller/base-controller.abstract.ts b/src/shared/libs/rest/controller/base-controller.abstract.ts new file mode 100644 index 0000000..bc53d2c --- /dev/null +++ b/src/shared/libs/rest/controller/base-controller.abstract.ts @@ -0,0 +1,55 @@ +import { Response, Router } from 'express'; +import { Logger } from '../../logger/index.js'; +import { Route, Controller } from './../index.js'; +import { StatusCodes } from 'http-status-codes'; +import { inject, injectable } from 'inversify'; +import asyncHandler from 'express-async-handler'; +import { PathTransformer } from '../transform/path-transformer.js'; +import { Component } from '../../../types/index.js'; +@injectable() +export abstract class BaseController implements Controller { + private readonly DEFAULT_CONTENT_TYPE = 'application/json'; + private readonly _router: Router; + + @inject(Component.PathTransformer) + private pathTransformer: PathTransformer; + + constructor( + protected readonly logger: Logger + ) { + this._router = Router(); + } + + get router() { + return this._router; + } + + public addRoute(route: Route): void { + const wrapperAsyncHandler = asyncHandler(route.handler.bind(this)); + + const middlewareHandlers = route.middlewares?.map( + (item) => asyncHandler(item.execute.bind(item)) + ); + const allHandlers = middlewareHandlers ? [... middlewareHandlers, wrapperAsyncHandler] : wrapperAsyncHandler; + + this._router[route.method](route.path, allHandlers); + this.logger.info(`Route registered: ${route.method.toUpperCase()} ${route.path}`); + } + + public send(res: Response, statusCode: number, data: T): void { + const modifieData = this.pathTransformer.execute(data as Record); + res.type(this.DEFAULT_CONTENT_TYPE).status(statusCode).json(modifieData); + } + + public created(res: Response, data: T): void { + this.send(res, StatusCodes.CREATED, data); + } + + public noContent(res: Response, data: T): void { + this.send(res, StatusCodes.NO_CONTENT, data); + } + + public ok(res: Response, data: T): void { + this.send(res, StatusCodes.OK, data); + } +} diff --git a/src/shared/libs/rest/controller/controller.interfase.ts b/src/shared/libs/rest/controller/controller.interfase.ts new file mode 100644 index 0000000..f6afc6e --- /dev/null +++ b/src/shared/libs/rest/controller/controller.interfase.ts @@ -0,0 +1,11 @@ +import { Router, Response } from 'express'; +import { Route } from './../index.js'; + +export interface Controller { + readonly router: Router; + addRoute(route: Route): void; + send(res: Response, statusCode: number, data: T): void; + ok(res: Response, data: T): void; + created(res: Response, data: T): void; + noContent(res: Response, data: T): void; +} diff --git a/src/shared/libs/rest/errors/http-error.ts b/src/shared/libs/rest/errors/http-error.ts new file mode 100644 index 0000000..db565a8 --- /dev/null +++ b/src/shared/libs/rest/errors/http-error.ts @@ -0,0 +1,12 @@ +export class HttpError extends Error { + public httpStatusCode!: number; + public detail?: string; + + constructor(httpStatusCode: number, message: string, detail?: string) { + super(message); + + this.httpStatusCode = httpStatusCode; + this.message = message; + this.detail = detail; + } +} diff --git a/src/shared/libs/rest/errors/validation.error.ts b/src/shared/libs/rest/errors/validation.error.ts new file mode 100644 index 0000000..e24200b --- /dev/null +++ b/src/shared/libs/rest/errors/validation.error.ts @@ -0,0 +1,11 @@ +import { StatusCodes } from 'http-status-codes'; +import { ValidationErrorField, HttpError } from './../index.js'; + +export class ValidationError extends HttpError { + public details?: ValidationErrorField[] = []; + + constructor(message: string, errors: ValidationErrorField[]) { + super(StatusCodes.BAD_REQUEST, message); + this.details = errors; + } +} diff --git a/src/shared/libs/rest/exception-filter/app-exception-filter.ts b/src/shared/libs/rest/exception-filter/app-exception-filter.ts new file mode 100644 index 0000000..53899be --- /dev/null +++ b/src/shared/libs/rest/exception-filter/app-exception-filter.ts @@ -0,0 +1,24 @@ +import { inject, injectable } from 'inversify'; +import { ExceptionFilter, ApplicationError } from './../index.js'; +import { Component } from '../../../types/index.js'; +import { Logger } from '../../logger/index.js'; +import { Request, Response, NextFunction } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import { createErrorObject } from '../../../helpers/index.js'; + + +@injectable() +export class AppExceptionFilter implements ExceptionFilter { + constructor( + @inject(Component.Logger) private readonly logger: Logger + ) { + this.logger.info('Register AppExceptionFilter'); + } + + public catch(error: Error, _req: Request, res: Response, _next: NextFunction): void { + this.logger.error(error.message, error); + res + .status(StatusCodes.INTERNAL_SERVER_ERROR) + .json(createErrorObject(ApplicationError.ServiceError, error.message)); + } +} diff --git a/src/shared/libs/rest/exception-filter/auth-exception-filter.ts b/src/shared/libs/rest/exception-filter/auth-exception-filter.ts new file mode 100644 index 0000000..ad36160 --- /dev/null +++ b/src/shared/libs/rest/exception-filter/auth-exception-filter.ts @@ -0,0 +1,28 @@ +import { inject, injectable } from 'inversify'; +import { ExceptionFilter } from'./../index.js'; +import { Component } from '../../../types/index.js'; +import { Logger } from '../../logger/index.js'; +import { Request, Response, NextFunction } from 'express'; +import { BaseUserException } from '../../../modules/auth/index.js'; + +@injectable() +export class AuthExceptionFilter implements ExceptionFilter { + constructor( + @inject(Component.Logger) private readonly logger: Logger + ) { + this.logger.info('Register AuthExceptionFilter'); + } + + public catch(error: Error, _req: Request, res: Response, next: NextFunction): void { + if (! (error instanceof BaseUserException)) { + return next(error); + } + + this.logger.error(`[AuthModule] ${error.message}`, error); + res.status(error.httpStatusCode).json({ + type: 'AUTHORIZATION', + error: error.message + }); + } +} + diff --git a/src/shared/libs/rest/exception-filter/exception-filter.interface.ts b/src/shared/libs/rest/exception-filter/exception-filter.interface.ts new file mode 100644 index 0000000..8aaecd0 --- /dev/null +++ b/src/shared/libs/rest/exception-filter/exception-filter.interface.ts @@ -0,0 +1,5 @@ +import { Request, Response, NextFunction } from 'express'; + +export interface ExceptionFilter { + catch(error: Error, req: Request, res: Response, next: NextFunction): void +} diff --git a/src/shared/libs/rest/exception-filter/http-error-exception-filter.ts b/src/shared/libs/rest/exception-filter/http-error-exception-filter.ts new file mode 100644 index 0000000..0acd071 --- /dev/null +++ b/src/shared/libs/rest/exception-filter/http-error-exception-filter.ts @@ -0,0 +1,26 @@ +import { inject, injectable } from 'inversify'; +import { ExceptionFilter, ApplicationError, HttpError } from './../index.js'; +import { Request, Response, NextFunction } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import { createErrorObject } from '../../../helpers/index.js'; +import { Component } from '../../../types/index.js'; +import { Logger } from '../../logger/index.js'; + +@injectable() +export class HttpErrorExceptionFilter implements ExceptionFilter { + constructor( + @inject(Component.Logger) private readonly logger: Logger + ) { + this.logger.info('Register HttpErrorExceptionFilter'); + } + + public catch(error: unknown, req: Request, res: Response, next: NextFunction): void { + if (! (error instanceof HttpError)) { + return next(error); + } + this.logger.error(`[HttpErrorException]: ${req.path} # ${error.message}`, error); + res + .status(StatusCodes.BAD_REQUEST) + .json(createErrorObject(ApplicationError.CommonError, error.message)); + } +} diff --git a/src/shared/libs/rest/exception-filter/validation-exception-filter.ts b/src/shared/libs/rest/exception-filter/validation-exception-filter.ts new file mode 100644 index 0000000..ede3097 --- /dev/null +++ b/src/shared/libs/rest/exception-filter/validation-exception-filter.ts @@ -0,0 +1,35 @@ +import { inject, injectable } from 'inversify'; +import { ExceptionFilter } from './../index.js'; +import { Component } from '../../../types/index.js'; +import { Logger } from '../../logger/index.js'; +import { Request, Response, NextFunction } from 'express'; +import { ValidationError, ApplicationError } from './../index.js'; +import { StatusCodes } from 'http-status-codes'; +import { createErrorObject } from '../../../helpers/index.js'; + + +@injectable() +export class ValodationExceptionFilter implements ExceptionFilter { + + constructor( + @inject(Component.Logger) private readonly logger: Logger + ) { + this.logger.info('Register ValodationExceptionFilter'); + } + + public catch(error: unknown, _req: Request, res: Response, next: NextFunction): void { + if (! (error instanceof ValidationError)) { + return next(error); + } + + this.logger.error(`[ValodationException]: ${error.message}`, error); + + error.details?.forEach( + (errorField) => this.logger.warn(`[${errorField.property}] - ${errorField.messages}`) + ); + + res + .status(StatusCodes.BAD_REQUEST) + .json(createErrorObject(ApplicationError.ValidationError, error.message, error.details)); + } +} diff --git a/src/shared/libs/rest/index.ts b/src/shared/libs/rest/index.ts new file mode 100644 index 0000000..bc91545 --- /dev/null +++ b/src/shared/libs/rest/index.ts @@ -0,0 +1,24 @@ +export { HttpMethod } from './types/http-method.enum.js'; +export { Route } from './types/route.interface.js'; +export { Controller } from './controller/controller.interfase.js'; +export { BaseController } from './controller/base-controller.abstract.js'; +export { ExceptionFilter } from './exception-filter/exception-filter.interface.js'; +export { AppExceptionFilter,} from './exception-filter/app-exception-filter.js'; +export { HttpError } from './errors/http-error.js'; +export { RequestBody } from './types/request-body.type.js'; +export { RequestParams } from './types/request.params.type.js'; +export { Middleware } from './middleware/middleware.interface.js'; +export { ParseTokenMiddleware } from './middleware/parse-token.middleware.js'; +export { AuthExceptionFilter } from './exception-filter/auth-exception-filter.js'; +export { HttpErrorExceptionFilter } from './exception-filter/http-error-exception-filter.js'; +export { ValodationExceptionFilter } from './exception-filter/validation-exception-filter.js'; +export { DEFAULT_STATIC_IMAGES, STATIC_RESOURCE_FIELDS } from './transform/path-transformer.constant.js'; +export { PathTransformer } from './transform/path-transformer.js'; +export { ValidationErrorField } from './types/validation-error-field.js'; +export { ApplicationError } from './types/application-error.enum.js'; +export { ValidationError } from './errors/validation.error.js'; +export { ValidateDtoMiddleware } from './middleware/validate-dto.middleware.js'; +export { PrivateRouteMiddleware } from './middleware/private-route.middleware.js'; +export { DocumentExistsMiddleware } from './middleware/document-exists.middleware.js'; +export { ValidateObjectIdMiddleware } from './middleware/validate-objectid.middleware.js'; +export { UploadFileMiddleware } from './middleware/upload-file.middlewate.js'; diff --git a/src/shared/libs/rest/middleware/document-exists.middleware.ts b/src/shared/libs/rest/middleware/document-exists.middleware.ts new file mode 100644 index 0000000..911f9b3 --- /dev/null +++ b/src/shared/libs/rest/middleware/document-exists.middleware.ts @@ -0,0 +1,26 @@ +import { Request, Response, NextFunction } from 'express'; +import { DocumentExists } from '../../../types/index.js'; +import { Middleware, HttpError } from './../index.js'; +import { StatusCodes } from 'http-status-codes'; + +export class DocumentExistsMiddleware implements Middleware { + constructor( + private readonly service: DocumentExists, + private readonly entityName: string, + private readonly paramName: string, + ) {} + + public async execute({ params }: Request, _res: Response, next: NextFunction): Promise { + const documentId = params[this.paramName]; + + if (! await this.service.exists(documentId)) { + throw new HttpError( + StatusCodes.NOT_FOUND, + `${this.entityName} with ${documentId} not found`, + 'DocumentExistsMiddleware' + ); + } + + next(); + } +} diff --git a/src/shared/libs/rest/middleware/middleware.interface.ts b/src/shared/libs/rest/middleware/middleware.interface.ts new file mode 100644 index 0000000..107964e --- /dev/null +++ b/src/shared/libs/rest/middleware/middleware.interface.ts @@ -0,0 +1,5 @@ +import { Request, Response, NextFunction } from 'express'; + +export interface Middleware { + execute(req: Request, res: Response, next: NextFunction): void +} diff --git a/src/shared/libs/rest/middleware/parse-token.middleware.ts b/src/shared/libs/rest/middleware/parse-token.middleware.ts new file mode 100644 index 0000000..7ee543d --- /dev/null +++ b/src/shared/libs/rest/middleware/parse-token.middleware.ts @@ -0,0 +1,50 @@ +import { Request, Response, NextFunction } from 'express'; +import { Middleware } from './middleware.interface.js'; +import { createSecretKey } from 'node:crypto'; +import { jwtVerify } from 'jose'; +import { TokenPayload } from '../../../modules/auth/index.js'; +import { StatusCodes } from 'http-status-codes'; +import { HttpError } from '../index.js'; + +function isTokenPayload(payload: unknown): payload is TokenPayload { + return ( + (typeof payload === 'object' && payload !== null) && + ('email' in payload && typeof payload.email === 'string') && + ('name' in payload && typeof payload.name === 'string') && + ('isPro' in payload && typeof payload.isPro === 'boolean') && + ('id' in payload && typeof payload.id === 'string') + ); +} + +export class ParseTokenMiddleware implements Middleware { + + constructor( + private readonly jwtSecret: string + ) {} + + public async execute(req: Request, _res: Response, next: NextFunction): Promise { + const authorizationHeader = req.headers?.authorization?.split(' '); + if (!authorizationHeader) { + return next(); + } + + const [, token] = authorizationHeader; + + + try { + const { payload } = await jwtVerify(token, createSecretKey(this.jwtSecret, 'utf-8')); + if(isTokenPayload(payload)) { + req.tokenPayload = { ...payload }; + return next(); + } else { + throw new Error('Bad token'); + } + } catch { + return next(new HttpError( + StatusCodes.UNAUTHORIZED, + 'Invalid token', + 'AuthenticateMiddleware') + ); + } + } +} diff --git a/src/shared/libs/rest/middleware/private-route.middleware.ts b/src/shared/libs/rest/middleware/private-route.middleware.ts new file mode 100644 index 0000000..322a920 --- /dev/null +++ b/src/shared/libs/rest/middleware/private-route.middleware.ts @@ -0,0 +1,17 @@ +import { Request, Response, NextFunction } from 'express'; +import { Middleware, HttpError} from '../index.js'; +import { StatusCodes } from 'http-status-codes'; + +export class PrivateRouteMiddleware implements Middleware { + public async execute({ tokenPayload }: Request, _res: Response, next: NextFunction): Promise { + if (! tokenPayload) { + throw new HttpError( + StatusCodes.UNAUTHORIZED, + 'Unauthorized', + 'PrivateRouteMiddleware' + ); + } + + return next(); + } +} diff --git a/src/shared/libs/rest/middleware/upload-file.middlewate.ts b/src/shared/libs/rest/middleware/upload-file.middlewate.ts new file mode 100644 index 0000000..eba4095 --- /dev/null +++ b/src/shared/libs/rest/middleware/upload-file.middlewate.ts @@ -0,0 +1,27 @@ +import { Request, Response, NextFunction } from 'express'; +import { Middleware } from '../index.js'; +import multer, {diskStorage} from 'multer'; +import { extension } from 'mime-types'; + + +export class UploadFileMiddleware implements Middleware { + constructor( + private uploadDirectory: string, + private fieldName: string + ) {} + + public async execute(req: Request, res: Response, next: NextFunction): Promise { + const storage = diskStorage({ + destination: this.uploadDirectory, + filename: (_req, file, callback) => { + const fileExtention = extension(file.mimetype); + const filename = crypto.randomUUID(); + callback(null, `${filename}.${fileExtention}`); + } + }); + + const uploadSingleFileMiddleware = multer({ storage }).single(this.fieldName); + + uploadSingleFileMiddleware(req, res, next); + } +} diff --git a/src/shared/libs/rest/middleware/validate-dto.middleware.ts b/src/shared/libs/rest/middleware/validate-dto.middleware.ts new file mode 100644 index 0000000..478d6fd --- /dev/null +++ b/src/shared/libs/rest/middleware/validate-dto.middleware.ts @@ -0,0 +1,22 @@ +import { ClassConstructor, plainToInstance } from 'class-transformer'; +import { Middleware, ValidationError } from '../index.js'; +import { Request, Response, NextFunction } from 'express'; +import { validate } from 'class-validator'; +import { reduceValidationErrors } from '../../../helpers/index.js'; + +export class ValidateDtoMiddleware implements Middleware { + constructor( + private dto: ClassConstructor + ) {} + + public async execute({ body, path }: Request, _res: Response, next: NextFunction): Promise { + const dtoInstance = plainToInstance(this.dto, body); + const errors = await validate(dtoInstance); + + if (errors.length > 0) { + throw new ValidationError(`Validation error: ${path}`, reduceValidationErrors(errors)); + } + + next(); + } +} diff --git a/src/shared/libs/rest/middleware/validate-objectid.middleware.ts b/src/shared/libs/rest/middleware/validate-objectid.middleware.ts new file mode 100644 index 0000000..1436ed2 --- /dev/null +++ b/src/shared/libs/rest/middleware/validate-objectid.middleware.ts @@ -0,0 +1,26 @@ +import { Request, Response, NextFunction } from 'express'; +import { Middleware } from '../index.js'; +import { Types } from 'mongoose'; + +import { StatusCodes } from 'http-status-codes'; +import { HttpError } from '../errors/http-error.js'; + +export class ValidateObjectIdMiddleware implements Middleware { + constructor( + private param: string, + ) {} + + public execute({ params }: Request, _res: Response, next: NextFunction): void { + const mongoObjectId = params[this.param]; + + if(Types.ObjectId.isValid(mongoObjectId)) { + return next(); + } + + throw new HttpError( + StatusCodes.BAD_REQUEST, + `${mongoObjectId} is invalid ObjectID`, + 'ValidateObjectIdMiddleware' + ); + } +} diff --git a/src/shared/libs/rest/transform/path-transformer.constant.ts b/src/shared/libs/rest/transform/path-transformer.constant.ts new file mode 100644 index 0000000..0edfe67 --- /dev/null +++ b/src/shared/libs/rest/transform/path-transformer.constant.ts @@ -0,0 +1,11 @@ +export const DEFAULT_STATIC_IMAGES = [ + 'default-avatar.jpg', + 'static-image-1.jpg', + 'static-image-2.jpg', + 'static-image-3.jpg', + 'static-image-4.jpg', + 'static-image-5.jpg', + 'static-image-6.jpg' +]; + +export const STATIC_RESOURCE_FIELDS = ['avatarUser', 'images']; diff --git a/src/shared/libs/rest/transform/path-transformer.ts b/src/shared/libs/rest/transform/path-transformer.ts new file mode 100644 index 0000000..9cd1c06 --- /dev/null +++ b/src/shared/libs/rest/transform/path-transformer.ts @@ -0,0 +1,60 @@ +import { inject, injectable } from 'inversify'; +import { Component } from '../../../types/index.js'; +import { Logger } from '../../logger/index.js'; +import { Config } from 'convict'; +import { RestSchema } from '../../config/index.js'; +import { DEFAULT_STATIC_IMAGES, STATIC_RESOURCE_FIELDS } from '../index.js'; +import { STATIC_FILES_ROUTE, STATIC_UPLOAD_ROUTE } from '../../../../rest/index.js'; +import { getFullServerPath } from '../../../helpers/index.js'; + +function isObject(value: unknown): value is Record { + return typeof value === 'object' && value !== null; +} + +@injectable() +export class PathTransformer { + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.Config) private readonly config: Config + ) { + this.logger.info('PathTransformer created'); + } + + private hasDefaultImage(value: string) { + return DEFAULT_STATIC_IMAGES.includes(value); + } + + private isStaticProperty(property: string) { + return STATIC_RESOURCE_FIELDS.includes(property); + } + + public execute(data: Record): Record { + const stack = [data]; + while (stack.length > 0) { + const current = stack.pop(); + + for (const key in current) { + if (Object.hasOwn(current, key)) { + const value = current[key]; + + if (isObject(value)) { + stack.push(value); + continue; + } + + if (this.isStaticProperty(key) && typeof value === 'string') { + const staticPath = STATIC_FILES_ROUTE; + const uploadPath = STATIC_UPLOAD_ROUTE; + const serverHost = this.config.get('HOST'); + const serverPort = this.config.get('PORT'); + + const rootPath = this.hasDefaultImage(value) ? staticPath : uploadPath; + current[key] = `${getFullServerPath(serverHost, serverPort)}${rootPath}/${value}`; + } + } + } + } + + return data; + } +} diff --git a/src/shared/libs/rest/types/application-error.enum.ts b/src/shared/libs/rest/types/application-error.enum.ts new file mode 100644 index 0000000..1a2e3ee --- /dev/null +++ b/src/shared/libs/rest/types/application-error.enum.ts @@ -0,0 +1,5 @@ +export enum ApplicationError { + ValidationError = 'VALIDATION_ERROR', + CommonError = 'COMMON_ERROR', + ServiceError = 'SERVICE_ERROR', +} diff --git a/src/shared/libs/rest/types/http-method.enum.ts b/src/shared/libs/rest/types/http-method.enum.ts new file mode 100644 index 0000000..a4688a1 --- /dev/null +++ b/src/shared/libs/rest/types/http-method.enum.ts @@ -0,0 +1,7 @@ +export enum HttpMethod { + Get = 'get', + Post = 'post', + Delete = 'delete', + Patch = 'patch', + Put = 'put' +} diff --git a/src/shared/libs/rest/types/request-body.type.ts b/src/shared/libs/rest/types/request-body.type.ts new file mode 100644 index 0000000..bfe8e0e --- /dev/null +++ b/src/shared/libs/rest/types/request-body.type.ts @@ -0,0 +1 @@ +export type RequestBody = Record; diff --git a/src/shared/libs/rest/types/request.params.type.ts b/src/shared/libs/rest/types/request.params.type.ts new file mode 100644 index 0000000..4ac722d --- /dev/null +++ b/src/shared/libs/rest/types/request.params.type.ts @@ -0,0 +1 @@ +export type RequestParams = Record; diff --git a/src/shared/libs/rest/types/route.interface.ts b/src/shared/libs/rest/types/route.interface.ts new file mode 100644 index 0000000..0aa07cd --- /dev/null +++ b/src/shared/libs/rest/types/route.interface.ts @@ -0,0 +1,9 @@ +import { Middleware, HttpMethod } from '../index.js'; +import { Request, Response, NextFunction } from 'express'; + +export interface Route { + path: string; + method: HttpMethod; + handler: (req: Request, res: Response, next: NextFunction) => void; + middlewares?: Middleware[]; +} diff --git a/src/shared/libs/rest/types/validation-error-field.ts b/src/shared/libs/rest/types/validation-error-field.ts new file mode 100644 index 0000000..372facf --- /dev/null +++ b/src/shared/libs/rest/types/validation-error-field.ts @@ -0,0 +1,5 @@ +export type ValidationErrorField = { + property: string, + value: string, + messages: string[]; +} diff --git a/src/shared/modules/auth/auth-service.interface.ts b/src/shared/modules/auth/auth-service.interface.ts new file mode 100644 index 0000000..d0ff2be --- /dev/null +++ b/src/shared/modules/auth/auth-service.interface.ts @@ -0,0 +1,6 @@ +import { LoginUserDto, UserEntity } from '../user/index.js'; + +export interface AuthService { + authenticate(user: UserEntity): Promise; + verity(dto: LoginUserDto): Promise; +} diff --git a/src/shared/modules/auth/auth.container.ts b/src/shared/modules/auth/auth.container.ts new file mode 100644 index 0000000..a3acde3 --- /dev/null +++ b/src/shared/modules/auth/auth.container.ts @@ -0,0 +1,13 @@ +import { Container } from 'inversify'; +import { AuthService, DefaultAuthService } from './index.js'; +import { Component } from '../../types/index.js'; +import { AuthExceptionFilter, ExceptionFilter } from '../../libs/rest/index.js'; + +export function createAuthContainer() { + const container = new Container; + + container.bind(Component.AuthService).to(DefaultAuthService).inSingletonScope(); + container.bind(Component.AuthExceptionFilter).to(AuthExceptionFilter).inSingletonScope(); + + return container; +} diff --git a/src/shared/modules/auth/default-auth.service.ts b/src/shared/modules/auth/default-auth.service.ts new file mode 100644 index 0000000..e2a2cf8 --- /dev/null +++ b/src/shared/modules/auth/default-auth.service.ts @@ -0,0 +1,50 @@ +import { inject, injectable } from 'inversify'; +import { AuthService, UserNotFoundException, UserPasswordIncorrectException, TokenPayload, JWT_ALGORITHM, JWT_EXPIRED } from './index.js'; +import { Component } from '../../types/index.js'; +import { Logger } from '../../libs/logger/logger.interface.js'; +import { Config, RestSchema } from '../../libs/config/index.js'; +import { UserService, UserEntity, LoginUserDto } from '../user/index.js'; +import * as crypto from 'node:crypto'; +import { SignJWT } from 'jose'; + +@injectable() +export class DefaultAuthService implements AuthService { + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.Config) private readonly config: Config, + @inject(Component.UserService) private readonly userService: UserService, + ) {} + + public async authenticate(user: UserEntity): Promise { + const jwtSecret = this.config.get('JWT_SECRET'); + const secretKey = crypto.createSecretKey(jwtSecret, 'utf-8'); + const tokenPayload: TokenPayload = { + id: user.id, + email: user.email, + name: user.name, + isPro: user.isPro + }; + + this.logger.info(`Create token for ${user.email}`); + return new SignJWT(tokenPayload) + .setProtectedHeader({ alg: JWT_ALGORITHM }) + .setIssuedAt() + .setExpirationTime(JWT_EXPIRED) + .sign(secretKey); + } + + public async verity(dto: LoginUserDto): Promise { + const user = await this.userService.findByEmail(dto.email); + if (! user) { + this.logger.warn(`User with ${dto.email} not found`); + throw new UserNotFoundException(); + } + + if (! user.verifyPassword(dto.password, this.config.get('SALT'))) { + this.logger.warn(`Incorrect password for ${dto.email}`); + throw new UserPasswordIncorrectException(); + } + + return user; + } +} diff --git a/src/shared/modules/auth/helpers/const.ts b/src/shared/modules/auth/helpers/const.ts new file mode 100644 index 0000000..269747d --- /dev/null +++ b/src/shared/modules/auth/helpers/const.ts @@ -0,0 +1,2 @@ +export const JWT_ALGORITHM = 'HS256'; +export const JWT_EXPIRED = '2d'; diff --git a/src/shared/modules/auth/helpers/errors/base-user.exception.ts b/src/shared/modules/auth/helpers/errors/base-user.exception.ts new file mode 100644 index 0000000..362cdfa --- /dev/null +++ b/src/shared/modules/auth/helpers/errors/base-user.exception.ts @@ -0,0 +1,7 @@ +import { HttpError } from '../../../../libs/rest/index.js'; + +export class BaseUserException extends HttpError { + constructor(httpStatusCode: number, message: string) { + super(httpStatusCode, message); + } +} diff --git a/src/shared/modules/auth/helpers/errors/user-not-found.exception.ts b/src/shared/modules/auth/helpers/errors/user-not-found.exception.ts new file mode 100644 index 0000000..68941b5 --- /dev/null +++ b/src/shared/modules/auth/helpers/errors/user-not-found.exception.ts @@ -0,0 +1,8 @@ +import { StatusCodes } from 'http-status-codes'; +import { BaseUserException } from '../../index.js'; + +export class UserNotFoundException extends BaseUserException { + constructor() { + super(StatusCodes.NOT_FOUND, 'User not found'); + } +} diff --git a/src/shared/modules/auth/helpers/errors/user-password-incorrect.exception.ts b/src/shared/modules/auth/helpers/errors/user-password-incorrect.exception.ts new file mode 100644 index 0000000..c4ee81e --- /dev/null +++ b/src/shared/modules/auth/helpers/errors/user-password-incorrect.exception.ts @@ -0,0 +1,8 @@ +import { StatusCodes } from 'http-status-codes'; +import { BaseUserException } from '../../index.js'; + +export class UserPasswordIncorrectException extends BaseUserException { + constructor() { + super(StatusCodes.UNAUTHORIZED, 'Incorrect user name or password'); + } +} diff --git a/src/shared/modules/auth/helpers/types.ts b/src/shared/modules/auth/helpers/types.ts new file mode 100644 index 0000000..3d8b98b --- /dev/null +++ b/src/shared/modules/auth/helpers/types.ts @@ -0,0 +1,6 @@ +export type TokenPayload = { + id: string; + name: string; + email: string; + isPro: boolean +}; diff --git a/src/shared/modules/auth/index.ts b/src/shared/modules/auth/index.ts new file mode 100644 index 0000000..34d4f7c --- /dev/null +++ b/src/shared/modules/auth/index.ts @@ -0,0 +1,8 @@ +export { BaseUserException} from './helpers/errors/base-user.exception.js'; +export { TokenPayload } from './helpers/types.js'; +export { AuthService } from './auth-service.interface.js'; +export { DefaultAuthService } from './default-auth.service.js'; +export { JWT_ALGORITHM, JWT_EXPIRED } from './helpers/const.js'; +export { UserNotFoundException } from './helpers/errors/user-not-found.exception.js'; +export { UserPasswordIncorrectException } from './helpers/errors/user-password-incorrect.exception.js'; +export { createAuthContainer } from './auth.container.js'; diff --git a/src/shared/modules/comment/comment-servise.interface.ts b/src/shared/modules/comment/comment-servise.interface.ts new file mode 100644 index 0000000..a118a35 --- /dev/null +++ b/src/shared/modules/comment/comment-servise.interface.ts @@ -0,0 +1,8 @@ +import { DocumentType } from '@typegoose/typegoose'; +import { CommentEntity, CreateCommentDto } from './index.js'; + +export interface CommentService { + create(dto: CreateCommentDto): Promise>; + findByOfferId(offerId: string): Promise[]>; + deleteByOfferId(offerId: string): Promise; +} diff --git a/src/shared/modules/comment/comment.container.ts b/src/shared/modules/comment/comment.container.ts new file mode 100644 index 0000000..304b541 --- /dev/null +++ b/src/shared/modules/comment/comment.container.ts @@ -0,0 +1,15 @@ +import { Component } from '../../types/index.js'; +import { CommentEntity, CommentModel, CommentController, CommentService, DefaultCommentService} from './index.js'; +import { types } from '@typegoose/typegoose'; +import { Container } from 'inversify'; +import { Controller } from '../../libs/rest/index.js'; + +export function createCommentContainer() { + const container = new Container(); + + container.bind(Component.CommentService).to(DefaultCommentService).inSingletonScope(); + container.bind>(Component.CommentModul).toConstantValue(CommentModel); + container.bind(Component.CommentController).to(CommentController).inSingletonScope(); + + return container; +} diff --git a/src/shared/modules/comment/comment.controller.ts b/src/shared/modules/comment/comment.controller.ts new file mode 100644 index 0000000..4e39477 --- /dev/null +++ b/src/shared/modules/comment/comment.controller.ts @@ -0,0 +1,47 @@ +import { inject, injectable } from 'inversify'; +import { Component } from '../../types/index.js'; +import { Logger } from '../../libs/logger/index.js'; +import { CommentService, CreateCommentRequest, CommentRdo, CreateCommentDto } from './index.js'; +import { OfferService } from '../offer/index.js'; +import { Response } from 'express'; +import { fillDTO } from '../../helpers/index.js'; +import { StatusCodes } from 'http-status-codes'; +import { ValidateDtoMiddleware, PrivateRouteMiddleware, BaseController, HttpError, HttpMethod } from '../../libs/rest/index.js'; + + +@injectable() +export class CommentController extends BaseController { + constructor( + @inject(Component.Logger) protected readonly logger: Logger, + @inject(Component.CommentService) private readonly commentService: CommentService, + @inject(Component.OfferService) private readonly offerService: OfferService, + ) { + super(logger); + + this.logger.info('Register routes for CommentController'); + + this.addRoute({ + path: '/', + method: HttpMethod.Post, + handler: this.create, + middlewares: [ + new PrivateRouteMiddleware(), + new ValidateDtoMiddleware(CreateCommentDto) + ] + }); + } + + public async create({ body, tokenPayload }: CreateCommentRequest, res: Response): Promise { + if (! await this.offerService.exists(body.offerId)) { + throw new HttpError( + StatusCodes.NOT_FOUND, + `Offer with id ${body.offerId} not found.`, + 'CommentController' + ); + } + + const comment = await this.commentService.create({...body, userId: tokenPayload.id}); + await this.offerService.incCommentCount(body.offerId); + this.created(res, fillDTO(CommentRdo, comment)); + } +} diff --git a/src/shared/modules/comment/comment.entity.ts b/src/shared/modules/comment/comment.entity.ts new file mode 100644 index 0000000..2968f2c --- /dev/null +++ b/src/shared/modules/comment/comment.entity.ts @@ -0,0 +1,29 @@ +import { defaultClasses, getModelForClass, modelOptions, prop, Ref } from '@typegoose/typegoose'; +import { UserEntity } from '../user/index.js'; +import { OfferEntity } from '../offer/index.js'; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface CommentEntity extends defaultClasses.Base {} +@modelOptions({ + schemaOptions: { + collection: 'Comments', + timestamps: true, + }, +}) +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export class CommentEntity extends defaultClasses.TimeStamps { + + @prop({required: true, ref: OfferEntity}) + public offerId: Ref; + + @prop({required: true, minlength: 5, maxlength: 1024 }) + public comment: string; + + @prop({required: true, min: 1, max: 5}) + public rating: number; + + @prop({required: true, ref: UserEntity}) + public userId: Ref; +} + +export const CommentModel = getModelForClass(CommentEntity); diff --git a/src/shared/modules/comment/comment.http b/src/shared/modules/comment/comment.http new file mode 100644 index 0000000..125341b --- /dev/null +++ b/src/shared/modules/comment/comment.http @@ -0,0 +1,12 @@ +# Добавление комментария для предложения. +POST http://localhost:8796/comments/ HTTP/1.1 +Content-Type: application/json +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3MjVmZTQ3OWFiZGE1NmYyZjM1YTI0MiIsImVtYWlsIjoiYnkxQGluZGV4LmNvbSIsIm5hbWUiOiJEaW1hIiwiaXNQcm8iOnRydWUsImlhdCI6MTczMDU0MzE5NSwiZXhwIjoxNzMwNzE1OTk1fQ.1OyxdWbnJ7EoqUFzRpCca3b5XpyKOFoLJJPAj9rpQVE + +{ + "offerId": "67268766cb6688b18c5f55ff", + "comment": "A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.", + "rating": 5 +} + +### diff --git a/src/shared/modules/comment/default-comment.service.ts b/src/shared/modules/comment/default-comment.service.ts new file mode 100644 index 0000000..8494c5f --- /dev/null +++ b/src/shared/modules/comment/default-comment.service.ts @@ -0,0 +1,37 @@ +import { inject, injectable } from 'inversify'; +import { Component } from '../../types/index.js'; +import { Logger } from '../../libs/logger/index.js'; +import { types } from '@typegoose/typegoose'; +import { CommentEntity, CommentService, CreateCommentDto } from './index.js'; + +@injectable() +export class DefaultCommentService implements CommentService { + + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.CommentModul) private readonly commentModel: types.ModelType + ) {} + + public async create(dto: CreateCommentDto): Promise> { + const comment = await this.commentModel.create(dto); + this.logger.info('New comment created'); + + return comment.populate('userId'); + } + + public async findByOfferId(offerId: string, count?: number): Promise[]> { + const limit = count ?? 50; + const connets = this.commentModel.find({offerId}, {}, { limit }).populate('userId'); + this.logger.info(`List of comments for offer id:${offerId}`); + + return connets; + } + + public async deleteByOfferId(offerId: string): Promise { + const result = await this.commentModel.deleteMany({offerId}).exec(); + this.logger.info('The comment has been deleted'); + + return result.deletedCount; + } + +} diff --git a/src/shared/modules/comment/dto/create-comment.dto.ts b/src/shared/modules/comment/dto/create-comment.dto.ts new file mode 100644 index 0000000..2899086 --- /dev/null +++ b/src/shared/modules/comment/dto/create-comment.dto.ts @@ -0,0 +1,19 @@ +import { IsInt, IsMongoId, IsString, Max, MaxLength, Min, MinLength } from 'class-validator'; +import { CreateCommentMessage } from './create-comment.messages.js'; + +export class CreateCommentDto { + @IsMongoId({ message: CreateCommentMessage.offerId.invalidId }) + public offerId: string; + + @IsString({ message : CreateCommentMessage.comment.invalidFormat }) + @MinLength(5, { message: CreateCommentMessage.comment.minLength }) + @MaxLength(1024, { message: CreateCommentMessage.comment.maxLength }) + public comment: string; + + @IsInt({ message: CreateCommentMessage.rating.invalidFormat }) + @Min(1, { message: CreateCommentMessage.rating.min }) + @Max(5, { message: CreateCommentMessage.rating.max }) + public rating: number; + + public userId: string; +} diff --git a/src/shared/modules/comment/dto/create-comment.messages.ts b/src/shared/modules/comment/dto/create-comment.messages.ts new file mode 100644 index 0000000..e411aa2 --- /dev/null +++ b/src/shared/modules/comment/dto/create-comment.messages.ts @@ -0,0 +1,18 @@ +export const CreateCommentMessage = { + offerId: { + invalidId: 'offerId field must be a valid id' + }, + comment: { + invalidFormat: 'Comment is required', + minLength: 'Minimum title length must be 5', + maxLength: 'Maximum title length must be 1024', + }, + rating: { + invalidFormat: 'Rating is required', + min: 'Minimal rating 1', + max: 'Maximal rating 5' + }, + userId: { + invalidId: 'userId field must be a valid id' + } +} as const; diff --git a/src/shared/modules/comment/index.ts b/src/shared/modules/comment/index.ts new file mode 100644 index 0000000..78d47a9 --- /dev/null +++ b/src/shared/modules/comment/index.ts @@ -0,0 +1,9 @@ +export { CommentService } from './comment-servise.interface.js'; +export { createCommentContainer } from './comment.container.js'; +export { CommentEntity, CommentModel } from './comment.entity.js'; +export { DefaultCommentService } from './default-comment.service.js'; +export { CreateCommentDto } from './dto/create-comment.dto.js'; +export { CreateCommentMessage } from './dto/create-comment.messages.js'; +export { CommentController } from './comment.controller.js'; +export { CreateCommentRequest } from './types.js'; +export { CommentRdo } from './rdo/comment.rdo.js'; diff --git a/src/shared/modules/comment/rdo/comment.rdo.ts b/src/shared/modules/comment/rdo/comment.rdo.ts new file mode 100644 index 0000000..c780499 --- /dev/null +++ b/src/shared/modules/comment/rdo/comment.rdo.ts @@ -0,0 +1,19 @@ +import { Expose, Type } from 'class-transformer'; +import { UserRdo } from '../../user/index.js'; + +export class CommentRdo { + public id: string; + + @Expose({ name: 'createdAt'}) + public date: Date; + + @Expose() + public comment: string; + + @Expose() + public rating: number; + + @Expose({ name: 'userId'}) + @Type(() => UserRdo) + public user: UserRdo; +} diff --git a/src/shared/modules/comment/types.ts b/src/shared/modules/comment/types.ts new file mode 100644 index 0000000..7b8097e --- /dev/null +++ b/src/shared/modules/comment/types.ts @@ -0,0 +1,5 @@ +import { Request } from 'express'; +import { RequestBody, RequestParams } from '../../libs/rest/index.js'; +import { CreateCommentDto } from './index.js'; + +export type CreateCommentRequest = Request; diff --git a/src/shared/modules/offer/default-offer.service.ts b/src/shared/modules/offer/default-offer.service.ts new file mode 100644 index 0000000..b09709f --- /dev/null +++ b/src/shared/modules/offer/default-offer.service.ts @@ -0,0 +1,72 @@ +import { inject, injectable } from 'inversify'; +import { OfferService, CreateOfferDto, UpdateOfferDto, OfferEntity } from './index.js'; +import { Component } from '../../types/index.js'; +import { DocumentType, types } from '@typegoose/typegoose'; +import { Logger } from '../../libs/logger/index.js'; +import { location } from '../../helpers/index.js'; +import { DEFAULT_OFFER_IMAGES } from '../../helpers/const.js'; + + +@injectable() +export class DefaultOfferService implements OfferService { + + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.OfferModul) private readonly offerModel: types.ModelType + ) {} + + public async create(dto: CreateOfferDto): Promise> { + const offer = await this.offerModel.create({...dto, location: location[dto.city], images: DEFAULT_OFFER_IMAGES}); + this.logger.info(`New offer created: ${dto.title}`); + + return offer; + } + + public async deleteById(offerId: string): Promise | null> { + const offer = await this.offerModel.findByIdAndDelete(offerId).exec(); + this.logger.info(`${offerId} suggestion has been deleted.`); + + return offer; + } + + public async findOffers(count?: number): Promise[]> { + const limit = count ?? 60; + const offer = this.offerModel.find({}, {}, { limit }).exec(); + + this.logger.info(`List of offers ${offer}`); + + return offer; + } + + public async updateById(offerId: string, dto: UpdateOfferDto): Promise | null> { + return this.offerModel + .findByIdAndUpdate(offerId, dto, {new: true}) + .populate(['host']) + .exec(); + } + + public async findById(offerId: string): Promise | null> { + return this.offerModel.findById(offerId).populate(['host']).exec(); + } + + public async findByPremiumOffers(city: string, count?: number): Promise[] | null> { + const limit = count ?? 3; + return this.offerModel.find({city: city, isPremium: true}, {}, { limit }).populate(['host']).exec(); + } + + public async findByFavoritesOffers(): Promise[] | null> { + return this.offerModel.find({ isFavorite: true}, {}, {}).populate(['host']).exec(); + } + + public async incCommentCount(offerId: string): Promise | null> { + return this.offerModel + .findByIdAndUpdate(offerId, {'$inc': { + commentCount: 1, + }}).exec(); + } + + public async exists(documentId: string): Promise { + return (await this.offerModel + .exists({_id: documentId})) !== null; + } +} diff --git a/src/shared/modules/offer/dto/create-offer.dto.ts b/src/shared/modules/offer/dto/create-offer.dto.ts new file mode 100644 index 0000000..f72587f --- /dev/null +++ b/src/shared/modules/offer/dto/create-offer.dto.ts @@ -0,0 +1,52 @@ +import { MinLength, MaxLength, IsDateString, IsInt, Min, Max, IsEnum, IsBoolean, IsMongoId } from 'class-validator'; +import { CreateOfferValidationMessage } from './create-offer.messages.js'; +import { TypeOffer } from '../../../types/index.js'; + +export class CreateOfferDto { + @MinLength(10, { message: CreateOfferValidationMessage.title.minLength }) + @MaxLength(100, { message: CreateOfferValidationMessage.title.maxLength }) + public title: string; + + @MinLength(20, { message: CreateOfferValidationMessage.description.minLength }) + @MaxLength(1024, { message: CreateOfferValidationMessage.description.maxLength }) + public description: string; + + @IsDateString({}, { message: CreateOfferValidationMessage.postDate.invalidFormat }) + public postDate: Date; + + public city: string; + + @MaxLength(256, { message: CreateOfferValidationMessage.previewImage.maxLength }) + public previewImage: string; + + @IsBoolean({ message: CreateOfferValidationMessage.isPremium.invalidFormat }) + public isPremium: boolean; + + @IsInt({ message: CreateOfferValidationMessage.rating.invalidFormat }) + @Min(1, { message: CreateOfferValidationMessage.rating.min }) + @Max(5, { message: CreateOfferValidationMessage.rating.max }) + public rating: number; + + @IsEnum(TypeOffer, { message: CreateOfferValidationMessage.type.invalid}) + public type: TypeOffer; + + @IsInt({ message: CreateOfferValidationMessage.bedrooms.invalidFormat }) + @Min(1, { message: CreateOfferValidationMessage.bedrooms.min }) + @Max(8, { message: CreateOfferValidationMessage.bedrooms.max }) + public bedrooms: number; + + @IsInt({ message: CreateOfferValidationMessage.adults.invalidFormat }) + @Min(1, { message: CreateOfferValidationMessage.adults.min }) + @Max(10, { message: CreateOfferValidationMessage.adults.max }) + public maxAdults: number; + + @IsInt({ message: CreateOfferValidationMessage.price.invalidFormat }) + @Min(100, { message: CreateOfferValidationMessage.price.min }) + @Max(100000, { message: CreateOfferValidationMessage.price.max }) + public price: number; + + public goods: string[]; + + @IsMongoId({ message: CreateOfferValidationMessage.host.invalidId }) + public host: string; +} diff --git a/src/shared/modules/offer/dto/create-offer.messages.ts b/src/shared/modules/offer/dto/create-offer.messages.ts new file mode 100644 index 0000000..bdefbdb --- /dev/null +++ b/src/shared/modules/offer/dto/create-offer.messages.ts @@ -0,0 +1,53 @@ +export const CreateOfferValidationMessage = { + title: { + minLength: 'Minimum title length must be 10', + maxLength: 'Maximum title length must be 100', + }, + description: { + minLength: 'Minimum description length must be 20', + maxLength: 'Maximum description length must be 1024', + }, + postDate: { + invalidFormat: 'PostDate must be a valid ISO date', + }, + //city: {}, + previewImage:{ + maxLength: 'Too short for field «previewImage»', + }, + // images: + isFavorite: { + invalidFormat: 'isFavorite must be an false of true' + }, + isPremium: { + invalidFormat: 'isPremium must be an false of true' + }, + rating: { + invalidFormat: 'Rating must be an integer', + min: 'Minimal rating 1', + max: 'Maximal rating 5' + }, + type: { + invalid: 'Type of offers must be apartment, house, room, hotel' + }, + bedrooms: { + invalidFormat: 'Bedrooms must be an integer', + min: 'Minimal quantity bedroom is 1', + max: 'Maximal quantity bedrooms is 8' + }, + adults: { + invalidFormat: 'Adults must be an integer', + min: 'Minimal quantity adult is 1', + max: 'Maximal quantity adults is 10' + }, + price: { + invalidFormat: 'Price must be an integer', + min: 'Minimum price is 100', + max: 'Maximum price is 100 000' + }, + //goods: + host: { + invalidId: 'Host field must be a valid id', + }, +} as const; + + diff --git a/src/shared/modules/offer/dto/update-offer.dto.ts b/src/shared/modules/offer/dto/update-offer.dto.ts new file mode 100644 index 0000000..f58dbbd --- /dev/null +++ b/src/shared/modules/offer/dto/update-offer.dto.ts @@ -0,0 +1,74 @@ +import { IsBoolean, IsDateString, IsEnum, IsInt, IsMongoId, IsOptional, Max, MaxLength, Min, MinLength } from 'class-validator'; +import { UserRdo } from '../../user/index.js'; +import { UpdateOfferValidationMessage } from './update-offer.messages.js'; +import { TypeOffer } from '../../../types/index.js'; + + +export class UpdateOfferDto { + @IsOptional() + @MinLength(10, { message: UpdateOfferValidationMessage.title.minLength }) + @MaxLength(100, { message: UpdateOfferValidationMessage.title.maxLength }) + public title?: string; + + @IsOptional() + @MinLength(20, { message: UpdateOfferValidationMessage.description.minLength }) + @MaxLength(1024, { message: UpdateOfferValidationMessage.description.maxLength }) + public description?: string; + + @IsOptional() + @IsDateString({}, { message: UpdateOfferValidationMessage.postDate.invalidFormat }) + public postDate?: Date; + + @IsOptional() + public city?: string; + + @IsOptional() + @MaxLength(256, { message: UpdateOfferValidationMessage.previewImage.maxLength }) + public previewImage?: string; + + @IsOptional() + public images?: string[]; + + @IsOptional() + @IsBoolean({ message: UpdateOfferValidationMessage.isFavorite.invalidFormat }) + public isFavorite?: boolean; + + @IsOptional() + @IsBoolean({ message: UpdateOfferValidationMessage.isPremium.invalidFormat }) + public isPremium?: boolean; + + @IsOptional() + @IsInt({ message: UpdateOfferValidationMessage.rating.invalidFormat }) + @Min(1, { message: UpdateOfferValidationMessage.rating.min }) + @Max(5, { message: UpdateOfferValidationMessage.rating.max }) + public rating?: number; + + @IsOptional() + @IsEnum(TypeOffer, { message: UpdateOfferValidationMessage.type.invalid}) + public type?: TypeOffer; + + @IsOptional() + @IsInt({ message: UpdateOfferValidationMessage.bedrooms.invalidFormat }) + @Min(1, { message: UpdateOfferValidationMessage.bedrooms.min }) + @Max(8, { message: UpdateOfferValidationMessage.bedrooms.max }) + public bedrooms?: number; + + @IsOptional() + @IsInt({ message: UpdateOfferValidationMessage.adults.invalidFormat }) + @Min(1, { message: UpdateOfferValidationMessage.adults.min }) + @Max(10, { message: UpdateOfferValidationMessage.adults.max }) + public maxAdults?: number; + + @IsOptional() + @IsInt({ message: UpdateOfferValidationMessage.price.invalidFormat }) + @Min(100, { message: UpdateOfferValidationMessage.price.min }) + @Max(100000, { message: UpdateOfferValidationMessage.price.max }) + public price?: number; + + @IsOptional() + public goods?: string[]; + + @IsOptional() + @IsMongoId({ message: UpdateOfferValidationMessage.host.invalidId }) + public host?: UserRdo; +} diff --git a/src/shared/modules/offer/dto/update-offer.messages.ts b/src/shared/modules/offer/dto/update-offer.messages.ts new file mode 100644 index 0000000..ecaf3d2 --- /dev/null +++ b/src/shared/modules/offer/dto/update-offer.messages.ts @@ -0,0 +1,52 @@ +export const UpdateOfferValidationMessage = { + title: { + minLength: 'Minimum title length is 10', + maxLength: 'Maximum title length is 100' + }, + description: { + minLength: 'Minimum description length is 20', + maxLength: 'Maximum description length is 1024', + }, + postDate: { + invalidFormat: 'PostData must be a valid ISO date', + }, + // city: string; + previewImage: { + invalidFormat: 'Image is required', + maxLength: 'Too long for field image. Maximum length is 256' + }, + // public images?: Images[]; + isFavorite: { + invalidFormat: 'isFavorite must be is false or true' + }, + isPremium: { + invalidFormat: 'isPremium must be is false or true' + }, + rating: { + invalidFormat: 'Rating must be an integer', + min: 'Minimal rating 1', + max: 'Maximal rating 5' + }, + type: { + invalid: 'Type of offers must be apartment, house, room, hotel' + }, + bedrooms: { + invalidFormat: 'Bedrooms must be an integer', + min: 'Minimal quantity bedroom is 1', + max: 'Maximal quantity bedrooms is 8' + }, + adults: { + invalidFormat: 'Adults must be an integer', + min: 'Minimal quantity adult is 1', + max: 'Maximal quantity adults is 10' + }, + price: { + invalidFormat: 'price must be an integer', + min: 'Minimum price is 100', + max: 'Maximum price is 100 000' + }, + // public goods?: Goods[]; + host: { + invalidId: 'Host field must be a valid id', + }, +} as const; diff --git a/src/shared/modules/offer/index.ts b/src/shared/modules/offer/index.ts new file mode 100644 index 0000000..7ed672c --- /dev/null +++ b/src/shared/modules/offer/index.ts @@ -0,0 +1,11 @@ +export { OfferEntity, OfferModel } from './offer.entity.js'; +export { OfferController } from './offer.controller.js'; +export { createOfferContainer } from './offer.container.js'; +export {OfferService } from './offer-servise.interface.js'; +export { CreateOfferDto } from './dto/create-offer.dto.js'; +export { UpdateOfferDto } from './dto/update-offer.dto.js'; +export { DetailedOfferRdo } from './rdo/detailed-offer.rdo.js'; +export { OfferRdo } from './rdo/offer.rdo.js'; +export { DefaultOfferService } from './default-offer.service.js'; +export { CreareOfferRequest } from './types.js'; +export { ParamOfferCity, ParamOfferId, RequestQuery } from './types.js'; diff --git a/src/shared/modules/offer/offer-servise.interface.ts b/src/shared/modules/offer/offer-servise.interface.ts new file mode 100644 index 0000000..3707f4d --- /dev/null +++ b/src/shared/modules/offer/offer-servise.interface.ts @@ -0,0 +1,15 @@ +import { DocumentType } from '@typegoose/typegoose'; +import { OfferEntity, UpdateOfferDto, CreateOfferDto } from './index.js'; +import { DocumentExists } from '../../types/index.js'; + +export interface OfferService extends DocumentExists { + create(dto: CreateOfferDto): Promise>; + deleteById(offerId: string) : Promise | null>; + findOffers(): Promise[]>; + updateById(offerId: string, dto: UpdateOfferDto): Promise | null>; + findById(offerId: string): Promise | null>; + findByPremiumOffers(city: string, count?: number): Promise[] | null>; + findByFavoritesOffers(count?: number): Promise[] | null>; + incCommentCount(offerId: string): Promise | null>; + exists(offerId: string): Promise; +} diff --git a/src/shared/modules/offer/offer.container.ts b/src/shared/modules/offer/offer.container.ts new file mode 100644 index 0000000..325fcb1 --- /dev/null +++ b/src/shared/modules/offer/offer.container.ts @@ -0,0 +1,16 @@ +import { Container } from 'inversify'; +import { Component } from '../../types/index.js'; +import { types } from '@typegoose/typegoose'; +import { OfferEntity, OfferModel, OfferController, DefaultOfferService, OfferService} from './index.js'; +import { Controller } from '../../libs/rest/index.js'; + + +export function createOfferContainer() { + const container = new Container(); + + container.bind(Component.OfferService).to(DefaultOfferService).inSingletonScope(); + container.bind>(Component.OfferModul).toConstantValue(OfferModel); + container.bind(Component.OfferController).to(OfferController).inSingletonScope(); + + return container; +} diff --git a/src/shared/modules/offer/offer.controller.ts b/src/shared/modules/offer/offer.controller.ts new file mode 100644 index 0000000..32c7bb7 --- /dev/null +++ b/src/shared/modules/offer/offer.controller.ts @@ -0,0 +1,150 @@ +import { inject, injectable } from 'inversify'; +import { BaseController, HttpError, HttpMethod, PrivateRouteMiddleware, DocumentExistsMiddleware, ValidateDtoMiddleware, ValidateObjectIdMiddleware } from '../../libs/rest/index.js'; +import { Component } from '../../types/index.js'; +import { Logger } from '../../libs/logger/index.js'; +import { Request, Response } from 'express'; +import { fillDTO } from '../../helpers/index.js'; +import { CreareOfferRequest, CreateOfferDto, DetailedOfferRdo, OfferRdo, OfferService, UpdateOfferDto, ParamOfferCity, ParamOfferId, RequestQuery } from './index.js'; +import { CommentRdo, CommentService } from '../comment/index.js'; +import { StatusCodes } from 'http-status-codes'; + +@injectable() +export class OfferController extends BaseController { + constructor( + @inject(Component.Logger) protected readonly logger: Logger, + @inject(Component.OfferService) private readonly offerService: OfferService, + @inject(Component.CommentService) private readonly commentService: CommentService + ) { + super(logger); + + this.logger.info('Register routes for OfferController'); + + this.addRoute({ + path: '/', + method: HttpMethod.Post, + handler: this.create, + middlewares: [ + new PrivateRouteMiddleware(), + new ValidateDtoMiddleware(CreateOfferDto) + ] + }); + this.addRoute({ path: '/', method: HttpMethod.Get, handler: this.index }); + this.addRoute({ path: '/:city/offer', method: HttpMethod.Get, handler: this.premium }); + this.addRoute({ path: '/favorite/offers', method: HttpMethod.Get, handler: this.favorite }); + this.addRoute({ + path: '/:offerId', + method: HttpMethod.Get, + handler: this.show, + middlewares: [ + new ValidateObjectIdMiddleware('offerId'), + new DocumentExistsMiddleware(this.offerService, 'Offer', 'offerId') + ] + }); + this.addRoute({ + path: '/:offerId', + method: HttpMethod.Delete, + handler: this.delete, + middlewares: [ + new PrivateRouteMiddleware(), + new ValidateObjectIdMiddleware('offerId'), + new DocumentExistsMiddleware(this.offerService, 'Offer', 'offerId') + ] + }); + this.addRoute({ + path: '/:offerId', + method: HttpMethod.Put, + handler: this.update, + middlewares: [ + new PrivateRouteMiddleware(), + new ValidateObjectIdMiddleware('offerId'), + new ValidateDtoMiddleware(UpdateOfferDto), + new DocumentExistsMiddleware(this.offerService, 'Offer', 'offerId') + ] + }); + this.addRoute({ + path: '/:offerId/comments', + method: HttpMethod.Get, + handler: this.indexComment, + middlewares: [ + new ValidateObjectIdMiddleware('offerId'), + new DocumentExistsMiddleware(this.offerService, 'Offer', 'offerId') + ] + }); + this.addRoute({ + path: '/favorite/:offerId/', + method: HttpMethod.Post, + handler: this.favoriteStatus, + middlewares: [ + new PrivateRouteMiddleware(), + new ValidateObjectIdMiddleware('offerId'), + new DocumentExistsMiddleware(this.offerService, 'Offer', 'offerId') + ] + }); + } + + public async create({ body }: CreareOfferRequest, res: Response): Promise { + const offer = await this.offerService.create(body); + this.created(res, fillDTO(OfferRdo, offer)); + } + + public async index(_req: Request, res: Response): Promise { + const offers = await this.offerService.findOffers(); + this.ok(res, fillDTO(OfferRdo, offers)); + } + + public async show({ params }: Request, res: Response): Promise { + const { offerId } = params; + const offer = await this.offerService.findById(offerId); + this.ok(res, fillDTO(DetailedOfferRdo, offer)); + } + + public async delete({ params }: Request, res: Response): Promise { + const { offerId } = params; + const offer = await this.offerService.deleteById(offerId); + await this.commentService.deleteByOfferId(offerId); + this.noContent(res, offer); + } + + public async update({ body, params }: Request, res: Response): Promise { + const { offerId } = params; + + const offer = await this.offerService.updateById(offerId, body); + this.ok(res, fillDTO(DetailedOfferRdo, offer)); + } + + public async premium({ params, query }: Request, res: Response): Promise { + const { city } = params; + const { limit } = query; + const offer = await this.offerService.findByPremiumOffers(city, limit); + + if (offer?.length === 0) { + throw new HttpError( + StatusCodes.NOT_FOUND, + `There is no premium offers for ${city}`, + 'OfferController' + ); + } + this.ok(res, fillDTO(OfferRdo, offer)); + } + + public async favorite(_req: Request, res: Response): Promise { + const offer = await this.offerService.findByFavoritesOffers(); + this.ok(res, fillDTO(OfferRdo, offer)); + } + + public async indexComment({ params }: Request, res: Response): Promise { + const { offerId } = params; + const comments = await this.commentService.findByOfferId(offerId); + this.ok(res, fillDTO(CommentRdo, comments)); + } + + public async favoriteStatus({ params, query }: Request, res: Response): Promise { + if (Number(query.status) === 0) { + const offer = await this.offerService.updateById(params.offerId, {isFavorite: false}); + return this.ok(res, fillDTO(DetailedOfferRdo, offer)); + } + + const offer = await this.offerService.updateById(params.offerId, {isFavorite: true}); + this.ok(res, fillDTO(DetailedOfferRdo, offer)); + } +} diff --git a/src/shared/modules/offer/offer.entity.ts b/src/shared/modules/offer/offer.entity.ts new file mode 100644 index 0000000..d196fa4 --- /dev/null +++ b/src/shared/modules/offer/offer.entity.ts @@ -0,0 +1,82 @@ +import { defaultClasses, getModelForClass, modelOptions, prop, Ref } from '@typegoose/typegoose'; +import { UserEntity } from '../user/index.js'; +import { Severity } from '@typegoose/typegoose'; +import { City, TypeOffer } from '../../types/index.js'; + + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface OfferEntity extends defaultClasses.Base {} + +@modelOptions({ + schemaOptions: { + collection: 'Offer', + timestamps: true, + }, + options: { + allowMixed: Severity.ALLOW, + }, +}) + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export class OfferEntity extends defaultClasses.TimeStamps { + @prop({required: true, minlength: 10, maxlength: 100 }) + public title: string; + + @prop({required: true, minlength: 20, maxlength: 1024 }) + public description: string; + + @prop({required: true}) + public postDate: Date; + + @prop({required: true}) + public city: string; + + @prop({required: true}) + public previewImage: string; + + @prop({required: true}) + public images: string[]; + + @prop({required: true, default: false}) + public isFavorite: boolean; + + @prop({required: true}) + public isPremium: boolean; + + @prop({required: true}) + public rating: number; + + @prop({ + required: true, + type: () => String, + enum: TypeOffer + }) + public type: TypeOffer; + + @prop({required: true}) + public bedrooms: number; + + @prop({required: true}) + public maxAdults: number; + + @prop({required: true}) + public price: number; + + @prop({required: true}) + public goods: string[]; + + @prop({ + ref: UserEntity, + required: true + }) + public host: Ref; + + @prop({required: true, default: 0}) + public commentCount: number; + + @prop({required: true,}) + public location: City; + +} + +export const OfferModel = getModelForClass(OfferEntity); diff --git a/src/shared/modules/offer/offer.http b/src/shared/modules/offer/offer.http new file mode 100644 index 0000000..65afa65 --- /dev/null +++ b/src/shared/modules/offer/offer.http @@ -0,0 +1,62 @@ +# Получение списка предложений по аренде. +GET http://localhost:3000/offers/ HTTP/1.1 +Content-Type: application/json +### + +## Получение детальной информации о предложении. +GET http:/localhost:3000/offers/672a7ea9295c8968469eace4 HTTP/1.1 +Content-Type: application/json +### + +## Редактирование предложения. +PUT http://localhost:3000/offers/67240b4c548a63f8af547669 HTTP/1.1 +Content-Type: application/json +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3MjVmZTQ3OWFiZGE1NmYyZjM1YTI0MiIsImVtYWlsIjoiYnkxQGluZGV4LmNvbSIsIm5hbWUiOiJEaW1hIiwiaXNQcm8iOnRydWUsImlhdCI6MTczMDU0MzE5NSwiZXhwIjoxNzMwNzE1OTk1fQ.1OyxdWbnJ7EoqUFzRpCca3b5XpyKOFoLJJPAj9rpQVE +{ + "title": "Test title" +} +### + +## Создание нового предложения. +POST http://localhost:3000/offers/ HTTP/1.1 +Content-Type: application/json +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3MmE2ZTVmZDhmMmY1NjVkOGNiNTI2YSIsImVtYWlsIjoibmV3X3VzZXJAdGVzdC5jb20iLCJuYW1lIjoiQWRtaW4iLCJpc1BybyI6dHJ1ZSwiaWF0IjoxNzMwODM1MDQ4LCJleHAiOjE3MzEwMDc4NDh9.Ik1hgIxlyn9LSkyjxmkPCeKzov5SPVWU5z53yeTMpRI + +{ + "title": "Wood and stone place", + "type": "house", + "previewImage": "https://url-to-image/image.png", + "postDate": "2019-05-08T14:13:56.569Z", + "price": 1000, + "city": "Amsterdam", + "isPremium": false, + "rating": 5, + "description": "A quiet cozy and picturesque that hides behind a a river by the unique lightness of Amsterdam.", + "bedrooms": 3, + "goods": [ "Wi-Fi"], + "host": "67213016a0b648cd48150e1d", + "maxAdults": 4 +} + +## Удаление предложения. +DELETE http:/localhost:3000/offers/672a7275908b8c8fcbdd33a0 HTTP/1.1 +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3MmE2ZTVmZDhmMmY1NjVkOGNiNTI2YSIsImVtYWlsIjoibmV3X3VzZXJAdGVzdC5jb20iLCJuYW1lIjoiQWRtaW4iLCJpc1BybyI6dHJ1ZSwiaWF0IjoxNzMwODM1MjAzLCJleHAiOjE3MzEwMDgwMDN9.iHoqYDXN8hTQyiRDt5cl8zQ_OlTUWBNsKu2n8gf3wNo + +### + +## Получение списка комментариев для предложения. +GET http://localhost:3000/offers/67268766cb6688b18c5f55ff/comments HTTP/1.1 +### + +## Получение списка премиальных предложений для города. +GET http://localhost:3000/offers/Amsterdam/offer?limit=1 HTTP/1.1 +### + +## Получения списка предложений, добавленных в избранное. +GET http://localhost:3000/offers/favorite/offers HTTP/1.1 +### + +## +POST http://localhost:3000/offers/favorite/6721e6d79d0b87d45b266fcd/?status=1 HTTP/1.1 +Content-Type: application/json +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3MmE2ZTVmZDhmMmY1NjVkOGNiNTI2YSIsImVtYWlsIjoibmV3X3VzZXJAdGVzdC5jb20iLCJuYW1lIjoiQWRtaW4iLCJpc1BybyI6dHJ1ZSwiaWF0IjoxNzMwODM1MjAzLCJleHAiOjE3MzEwMDgwMDN9.iHoqYDXN8hTQyiRDt5cl8zQ_OlTUWBNsKu2n8gf3wNo diff --git a/src/shared/modules/offer/rdo/detailed-offer.rdo.ts b/src/shared/modules/offer/rdo/detailed-offer.rdo.ts new file mode 100644 index 0000000..fdb4eaf --- /dev/null +++ b/src/shared/modules/offer/rdo/detailed-offer.rdo.ts @@ -0,0 +1,63 @@ +import { Expose, Type } from 'class-transformer'; +import { UserRdo } from '../../user/index.js'; +import { City } from '../../../types/index.js'; + + +export class DetailedOfferRdo { + @Expose() + public id: string; + + @Expose() + public title: string; + + @Expose() + public description: string; + + @Expose() + public postDate: Date; + + @Expose() + public city: string; + + @Expose() + public location: City; + + @Expose() + public previewImage: string; + + @Expose() + public images: string[]; + + @Expose() + public isFavorite: boolean; + + @Expose() + public isPremium: boolean; + + @Expose() + public rating: number; + + @Expose() + public type: string; + + @Expose() + public bedrooms: number; + + @Expose() + public maxAdults: number; + + @Expose() + public price: number; + + @Expose() + public goods: string[]; + + @Expose() + @Type(() => UserRdo) + public host: UserRdo; + + @Expose() + public commentCount: number; + + +} diff --git a/src/shared/modules/offer/rdo/offer.rdo.ts b/src/shared/modules/offer/rdo/offer.rdo.ts new file mode 100644 index 0000000..25ff31e --- /dev/null +++ b/src/shared/modules/offer/rdo/offer.rdo.ts @@ -0,0 +1,43 @@ +import { Expose } from 'class-transformer'; +import { City } from '../../../types/index.js'; + +export class OfferRdo { + @Expose() + public id: string; + + @Expose() + public title: string; + + @Expose() + public postDate: Date; + + @Expose() + public city: string; + + @Expose() + public location: City; + + @Expose() + public previewImage: string; + + @Expose() + public isFavorite: boolean; + + @Expose() + public isPremium: boolean; + + @Expose() + public rating: number; + + @Expose() + public type: string; + + @Expose() + public price: number; + + @Expose() + public commentCount: number; + +} + + diff --git a/src/shared/modules/offer/types.ts b/src/shared/modules/offer/types.ts new file mode 100644 index 0000000..0746014 --- /dev/null +++ b/src/shared/modules/offer/types.ts @@ -0,0 +1,12 @@ +import { Request } from 'express'; +import { RequestBody, RequestParams } from '../../libs/rest/index.js'; +import { CreateOfferDto } from './index.js'; +import { ParamsDictionary } from 'express-serve-static-core'; + +export type CreareOfferRequest = Request; +export type ParamOfferId = { offerId: string } | ParamsDictionary; +export type ParamOfferCity = { city: string } | ParamsDictionary; +export type RequestQuery = { + limit?: number; + status?: number; +} diff --git a/src/shared/modules/user/default-user.service.ts b/src/shared/modules/user/default-user.service.ts new file mode 100644 index 0000000..f0150f8 --- /dev/null +++ b/src/shared/modules/user/default-user.service.ts @@ -0,0 +1,41 @@ +import { DocumentType, types } from '@typegoose/typegoose'; +import { UserService, UserEntity, CreateUserDto } from './index.js'; +import { inject, injectable } from 'inversify'; +import { Component } from '../../types/index.js'; +import { Logger } from '../../libs/logger/index.js'; +import { DEFAULT_AVATAR_FILE_NAME } from '../../helpers/index.js'; + +@injectable() +export class DefaultUserService implements UserService { + + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.UserModul) private readonly userModel: types.ModelType + ) {} + + public async create(dto: CreateUserDto, salt: string): Promise> { + const user = new UserEntity({... dto, avatarUser: DEFAULT_AVATAR_FILE_NAME}); + user.setPassword(dto.password, salt); + + const result = await this.userModel.create(user); + + this.logger.info(`New user created: ${user.email}`); + + return result; + } + + public async findByEmail(email: string): Promise | null> { + return this.userModel.findOne({email}); + } + + public async findOrCreate(dto: CreateUserDto, salt: string): Promise> { + const existedUser = await this.findByEmail(dto.email); + + if (existedUser) { + return existedUser; + } + + return this.create(dto, salt); + } + +} diff --git a/src/shared/modules/user/dto/create-user.dto.ts b/src/shared/modules/user/dto/create-user.dto.ts new file mode 100644 index 0000000..8467c2b --- /dev/null +++ b/src/shared/modules/user/dto/create-user.dto.ts @@ -0,0 +1,19 @@ +import { IsBoolean, IsEmail, IsString, Length, MaxLength, MinLength } from 'class-validator'; +import { CreateUserMessage } from './create-user.messages.js'; + +export class CreateUserDto { + @IsString({ message : CreateUserMessage.name.invalidFormat }) + @MinLength(1, { message: CreateUserMessage.name.minLength }) + @MaxLength(15, { message: CreateUserMessage.name.maxLength }) + public name: string; + + @IsEmail({}, { message: CreateUserMessage.email.invalidFormat }) + public email: string; + + @IsString({ message: CreateUserMessage.password.invalidFormat }) + @Length(6, 12, { message: CreateUserMessage.password.lengthField }) + public password: string; + + @IsBoolean({ message: CreateUserMessage.isPro.invalidFormat }) + public isPro: boolean; +} diff --git a/src/shared/modules/user/dto/create-user.messages.ts b/src/shared/modules/user/dto/create-user.messages.ts new file mode 100644 index 0000000..55a0980 --- /dev/null +++ b/src/shared/modules/user/dto/create-user.messages.ts @@ -0,0 +1,17 @@ +export const CreateUserMessage = { + name: { + invalidFormat: 'Name is required', + minLength: 'min length is 1', + maxLength: 'max is 15' + }, + email: { + invalidFormat: 'email must be a valid address' + }, + password: { + invalidFormat: 'password is required', + lengthField: 'min length for password is 6, max is 12' + }, + isPro: { + invalidFormat: 'isPro must be an false of true' + } +} as const; diff --git a/src/shared/modules/user/dto/login-user.dto.ts b/src/shared/modules/user/dto/login-user.dto.ts new file mode 100644 index 0000000..403561b --- /dev/null +++ b/src/shared/modules/user/dto/login-user.dto.ts @@ -0,0 +1,10 @@ +import { IsEmail, IsString } from 'class-validator'; +import { CreateLoginUserMessage } from './login-user.messages.js'; + +export class LoginUserDto { + @IsEmail({}, { message: CreateLoginUserMessage.email.invalidFormat }) + public email: string; + + @IsString({ message: CreateLoginUserMessage.password.invalidFormat }) + public password: string; +} diff --git a/src/shared/modules/user/dto/login-user.messages.ts b/src/shared/modules/user/dto/login-user.messages.ts new file mode 100644 index 0000000..daabdd7 --- /dev/null +++ b/src/shared/modules/user/dto/login-user.messages.ts @@ -0,0 +1,8 @@ +export const CreateLoginUserMessage = { + email: { + invalidFormat: 'email must be a valid address', + }, + password: { + invalidFormat: 'password is required', + } +} as const; diff --git a/src/shared/modules/user/index.ts b/src/shared/modules/user/index.ts new file mode 100644 index 0000000..af0d298 --- /dev/null +++ b/src/shared/modules/user/index.ts @@ -0,0 +1,10 @@ +export { CreateUserDto } from './dto/create-user.dto.js'; +export { UserRdo } from './rdo/user.rdo.js'; +export { DefaultUserService } from './default-user.service.js'; +export { UserService } from './user-service.interface.js'; +export { createUserContainer } from './user.container.js'; +export { UserController } from './user.controller.js'; +export { UserEntity, UserModel } from './user.entity.js'; +export { LoginUserDto } from './dto/login-user.dto.js'; +export { CreateUserRequest, LoginUserRequest} from './types.js'; +export { LoggedUserRdo } from './rdo/logged-use.rdo.js'; diff --git a/src/shared/modules/user/rdo/logged-use.rdo.ts b/src/shared/modules/user/rdo/logged-use.rdo.ts new file mode 100644 index 0000000..39b9230 --- /dev/null +++ b/src/shared/modules/user/rdo/logged-use.rdo.ts @@ -0,0 +1,15 @@ +import { Expose } from 'class-transformer'; + +export class LoggedUserRdo { + @Expose({ name: '_id'}) + public id: string; + + @Expose() + public email: string; + + @Expose() + public token: string; + + @Expose({ name: 'name'}) + public name: string; +} diff --git a/src/shared/modules/user/rdo/user.rdo.ts b/src/shared/modules/user/rdo/user.rdo.ts new file mode 100644 index 0000000..9eb657b --- /dev/null +++ b/src/shared/modules/user/rdo/user.rdo.ts @@ -0,0 +1,18 @@ +import { Expose } from 'class-transformer'; + +export class UserRdo { + @Expose() + public name: string; + + @Expose() + public email: string; + + @Expose() + public avatarUser: string; + + @Expose() + public isPro: boolean; + + @Expose() + public token: string; +} diff --git a/src/shared/modules/user/types.ts b/src/shared/modules/user/types.ts new file mode 100644 index 0000000..c409e75 --- /dev/null +++ b/src/shared/modules/user/types.ts @@ -0,0 +1,6 @@ +import { Request } from 'express'; +import { RequestBody, RequestParams } from '../../libs/rest/index.js'; +import { CreateUserDto, LoginUserDto} from './index.js'; + +export type CreateUserRequest = Request; +export type LoginUserRequest = Request; diff --git a/src/shared/modules/user/user-service.interface.ts b/src/shared/modules/user/user-service.interface.ts new file mode 100644 index 0000000..92d96b4 --- /dev/null +++ b/src/shared/modules/user/user-service.interface.ts @@ -0,0 +1,8 @@ +import { DocumentType } from '@typegoose/typegoose'; +import { UserEntity, CreateUserDto } from './index.js'; + +export interface UserService { + create(dto: CreateUserDto, salt: string): Promise> + findByEmail(email: string): Promise | null>; + findOrCreate(dto: CreateUserDto, salt: string): Promise> +} diff --git a/src/shared/modules/user/user.container.ts b/src/shared/modules/user/user.container.ts new file mode 100644 index 0000000..956a571 --- /dev/null +++ b/src/shared/modules/user/user.container.ts @@ -0,0 +1,15 @@ +import { Container } from 'inversify'; +import { Component } from '../../types/index.js'; +import { DefaultUserService, UserController, UserEntity, UserModel, UserService } from './index.js'; +import { types } from '@typegoose/typegoose'; +import { Controller } from '../../libs/rest/index.js'; + +export function createUserContainer() { + const container = new Container(); + + container.bind(Component.UserService).to(DefaultUserService).inSingletonScope(); + container.bind>(Component.UserModul).toConstantValue(UserModel); + container.bind(Component.UserController).to(UserController).inSingletonScope(); + + return container; +} diff --git a/src/shared/modules/user/user.controller.ts b/src/shared/modules/user/user.controller.ts new file mode 100644 index 0000000..92aca5d --- /dev/null +++ b/src/shared/modules/user/user.controller.ts @@ -0,0 +1,112 @@ +import { inject, injectable } from 'inversify'; +import { BaseController, HttpError, HttpMethod, PrivateRouteMiddleware } from '../../libs/rest/index.js'; +import { Component } from '../../types/index.js'; +import { Logger } from '../../libs/logger/index.js'; +import { Request, Response } from 'express'; +import { UserService, UserRdo, LoginUserRequest, CreateUserRequest, CreateUserDto, LoginUserDto} from './index.js'; +import { fillDTO } from '../../helpers/index.js'; +import { StatusCodes } from 'http-status-codes'; +import { UploadFileMiddleware, ValidateObjectIdMiddleware, ValidateDtoMiddleware } from '../../libs/rest/index.js'; +import { RestSchema, Config } from '../../libs/config/index.js'; +import { AuthService } from '../auth/index.js'; +import { LoggedUserRdo } from './index.js'; + + +@injectable() +export class UserController extends BaseController { + constructor( + @inject(Component.Logger) protected readonly logger: Logger, + @inject(Component.Config) protected readonly config: Config, + @inject(Component.UserService) private readonly userService: UserService, + @inject(Component.AuthService) private readonly authService: AuthService + ) { + super(logger); + + this.logger.info('Register routes for UserController'); + + this.addRoute({ + path: '/register', + method: HttpMethod.Post, + handler: this.create, + middlewares: [ + new ValidateDtoMiddleware(CreateUserDto) + ] + }); + this.addRoute({ + path: '/login', + method: HttpMethod.Post, + handler: this.login, + middlewares: [ + new ValidateDtoMiddleware(LoginUserDto) + ] + }); + this.addRoute({ + path: '/:userId/avatar', + method: HttpMethod.Post, + handler: this.uploadAvatar, + middlewares: [ + new ValidateObjectIdMiddleware('userId'), + new UploadFileMiddleware(this.config.get('UPLOAD_DIRECTIRY'), 'avatar') + + ] + }); + this.addRoute({ + path: '/login', + method: HttpMethod.Get, + handler: this.checkStatusAuthenticate + }); + this.addRoute({ + path: '/logout', + method: HttpMethod.Delete, + handler: this.logout, + middlewares: [ + new PrivateRouteMiddleware() + ] + }); + } + + public async create({ body }: CreateUserRequest, res: Response): Promise { + const existUser = await this.userService.findByEmail(body.email); + + if (existUser) { + throw new HttpError( + StatusCodes.CONFLICT, + `A user with the email ${body.email} already exists.`, + 'UserController' + ); + } + + const result = await this.userService.create(body, 'salt'); + this.created(res, fillDTO(UserRdo, result)); + } + + public async login({ body }: LoginUserRequest, res: Response): Promise { + const user = await this.authService.verity(body); + const token = await this.authService.authenticate(user); + const responseData = fillDTO(LoggedUserRdo, { + email: user.email, + token + }); + this.ok(res, fillDTO(LoggedUserRdo,responseData)); + } + + public async uploadAvatar(req: Request, res: Response) { + this.created(res, { filepath: req.file?.path }); + } + + public async checkStatusAuthenticate({ tokenPayload: { email } }: Request, res: Response) { + const user = await this.userService.findByEmail(email); + if (! user) { + throw new HttpError( + StatusCodes.UNAUTHORIZED, + 'Unauthorized', + 'UserController' + ); + } + this.ok(res, fillDTO(UserRdo, user)); + } + + public logout(_req: Request, res: Response){ + this.noContent(res, {}); + } +} diff --git a/src/shared/modules/user/user.entity.ts b/src/shared/modules/user/user.entity.ts new file mode 100644 index 0000000..3f0f8eb --- /dev/null +++ b/src/shared/modules/user/user.entity.ts @@ -0,0 +1,56 @@ +import { defaultClasses, getModelForClass, modelOptions, prop } from '@typegoose/typegoose'; +import { User } from '../../types/index.js'; +import { createSHA256 } from '../../helpers/index.js'; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface UserEntity extends defaultClasses.Base {} + +@modelOptions({ + schemaOptions: { + collection: 'Users', + timestamps: true, + } +}) + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export class UserEntity extends defaultClasses.TimeStamps implements User { + @prop({required: true}) + public name: string; + + @prop({ unique: true, required: true}) + public email: string; + + @prop({ default: ''}) + public avatarUser: string; + + @prop({ required: true}) + public password: string; + + @prop({ required: true }) + public isPro: boolean; + + constructor(userData: User) { + super(); + + this.name = userData.name; + this.email = userData.email; + this.avatarUser = userData.avatarUser; + this.isPro = userData.isPro; + } + + public setPassword(password: string, salt: string) { + this.password = createSHA256(password, salt); + } + + public getPassword() { + return this.password; + } + + public verifyPassword(password: string, salt: string) { + const hashPassword = createSHA256(password, salt); + return hashPassword === this.password; + } + +} + +export const UserModel = getModelForClass(UserEntity); diff --git a/src/shared/modules/user/user.http b/src/shared/modules/user/user.http new file mode 100644 index 0000000..eb52fa7 --- /dev/null +++ b/src/shared/modules/user/user.http @@ -0,0 +1,30 @@ +# Создание нового пользователя. +POST http://localhost:3000/users/register HTTP/1.1 +Content-Type: application/json + +{ + "email": "new_user123456@test.com", + "name": "Admin", + "isPro": true, + "password": "super!" +} +### + +## Вход в закрытую часть приложения. +POST http://localhost:3000/users/login HTTP/1.1 +Content-Type: application/json + +{ + "email": "new_user123456@test.com", + "password": "super!" +} +### + +## Проверка состояния пользователя. +GET http://localhost:3000/users/login HTTP/1.1 +Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJpZCI6IjY3MmE3ODdiOWJmZDIyY2U5MGU5MjRlOSIsImVtYWlsIjoibmV3X3VzZXIxMjM0NUB0ZXN0LmNvbSIsIm5hbWUiOiJBZG1pbiIsImlzUHJvIjp0cnVlLCJpYXQiOjE3MzA4MzY2MTcsImV4cCI6MTczMTAwOTQxN30.yPXUcmXt8GsIasBoZ7mWDSfU6HHDNRtEUL4kAnaT08g +### + +## Выход из закрытой части приложения. + +DELETE http://localhost:3000/users/logout HTTP/1.1 diff --git a/src/shared/types/component.enum.ts b/src/shared/types/component.enum.ts index 91157c1..5f6c882 100644 --- a/src/shared/types/component.enum.ts +++ b/src/shared/types/component.enum.ts @@ -1,5 +1,25 @@ export const Component = { RestApplication: Symbol.for('RestApplication'), Logger: Symbol.for('Logger'), - Config: Symbol.for('Config') + Config: Symbol.for('Config'), + DatabaseClient: Symbol.for('DatabaseClient'), + + UserService: Symbol.for('UserService'), + UserModul: Symbol.for('UserModul'), + UserController: Symbol.for('UserController'), + + OfferService: Symbol.for('OfferService'), + OfferModul: Symbol.for('OfferModul'), + OfferController: Symbol.for('OfferController'), + + CommentService: Symbol.for('CommentService'), + CommentModul: Symbol.for('CommentModul'), + CommentController: Symbol.for('CommentController'), + + ExceptionFilter: Symbol.for('ExceptionFilter'), + AuthService: Symbol.for('AuthService'), + AuthExceptionFilter: Symbol.for('AuthExceptionFilter'), + HttpExceptionFilter: Symbol.for('HttpExceptionFilter'), + ValidationExceptionFilter: Symbol.for('ValidationExceptionFilter'), + PathTransformer: Symbol.for('PathTransformer'), } as const; diff --git a/src/shared/types/document-exists.interface.ts b/src/shared/types/document-exists.interface.ts new file mode 100644 index 0000000..50e01cb --- /dev/null +++ b/src/shared/types/document-exists.interface.ts @@ -0,0 +1,3 @@ +export interface DocumentExists { + exists(documentId: string): Promise +} diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index 1247e3a..00c86a7 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -1,4 +1,7 @@ -export { Offer } from './offer-type.js'; -export { TypeUser, User } from './user-type.js'; +export { Offer, Goods, Images, TypeOffer } from './offer-type.js'; +export { User } from './user-type.js'; export { MockServerData } from './mock-server-data.js'; export { Component } from './component.enum.js'; +export { Location, City } from './location-type.js'; +export { DocumentExists } from './document-exists.interface.js'; + diff --git a/src/shared/types/location-type.ts b/src/shared/types/location-type.ts new file mode 100644 index 0000000..93cb038 --- /dev/null +++ b/src/shared/types/location-type.ts @@ -0,0 +1,8 @@ +export type Location = { + name: string; + latitude: number; + longitude: number; +} +export type City = { + [key: string] : Location +} diff --git a/src/shared/types/mock-server-data.ts b/src/shared/types/mock-server-data.ts index 36fb3f5..d9058d4 100644 --- a/src/shared/types/mock-server-data.ts +++ b/src/shared/types/mock-server-data.ts @@ -1,12 +1,12 @@ export type MockServerData = { - title: string[], - description: string[], - city: string[], - previewImage: string[], - images: string[], - type: string[], - goods: string[], + title: string[]; + description: string[]; + city: string[]; + previewImage: string[]; + images: string[]; + type: string[]; + goods: string[]; name: string[]; email: string[]; avatarUser: string[]; diff --git a/src/shared/types/offer-type.ts b/src/shared/types/offer-type.ts index 932e7e0..9450b21 100644 --- a/src/shared/types/offer-type.ts +++ b/src/shared/types/offer-type.ts @@ -1,26 +1,41 @@ -import { User } from './user-type.js'; +import { User, Location } from './index.js'; export type Images = { img: string; } -export type Goods = { - good: string; +export enum Goods { + Breakfast = 'Breakfast', + AirConditioning = 'Air conditioning', + LaptopFriendlyWorkspace = 'Laptop friendly workspace', + BabySeat = 'Baby seat', + Washer = 'Washer', + Towels = 'Towels', + Fridge = 'Fridge' } +export enum TypeOffer { + apartment = 'apartment', + house = 'house', + room = 'room', + hotel = 'hotel' +} + + export type Offer = { title: string; description: string; postDate: Date; city: string; previewImage: string; - images: Images[]; + images: string[]; isFavorite: boolean; isPremium: boolean; rating: number; - type: string; + type: TypeOffer; bedrooms: number; maxAdults: number; price: number; - goods: Goods[]; + goods: string[] | Goods[]; host: User; + location: Location } diff --git a/src/shared/types/user-type.ts b/src/shared/types/user-type.ts index 22aeee4..142c7e8 100644 --- a/src/shared/types/user-type.ts +++ b/src/shared/types/user-type.ts @@ -1,12 +1,7 @@ -export enum TypeUser { - Pro = 'Pro', - Usual = 'Usual' -} - export type User = { name: string; email: string; - avatarUser?: string; + avatarUser: string; password: string; - typeUser: TypeUser; + isPro: boolean; } diff --git a/static/default-avatar.jpg b/static/default-avatar.jpg new file mode 100644 index 0000000..7910564 Binary files /dev/null and b/static/default-avatar.jpg differ diff --git a/static/static-image-1.jpg b/static/static-image-1.jpg new file mode 100644 index 0000000..a03a576 Binary files /dev/null and b/static/static-image-1.jpg differ diff --git a/static/static-image-2.jpg b/static/static-image-2.jpg new file mode 100644 index 0000000..506079a Binary files /dev/null and b/static/static-image-2.jpg differ diff --git a/static/static-image-3.jpg b/static/static-image-3.jpg new file mode 100644 index 0000000..51dbd67 Binary files /dev/null and b/static/static-image-3.jpg differ diff --git a/static/static-image-4.jpg b/static/static-image-4.jpg new file mode 100644 index 0000000..1f4def1 Binary files /dev/null and b/static/static-image-4.jpg differ diff --git a/static/static-image-5.jpg b/static/static-image-5.jpg new file mode 100644 index 0000000..134e88f Binary files /dev/null and b/static/static-image-5.jpg differ diff --git a/static/static-image-6.jpg b/static/static-image-6.jpg new file mode 100644 index 0000000..ee18fd0 Binary files /dev/null and b/static/static-image-6.jpg differ diff --git a/tsconfig.json b/tsconfig.json index e061253..31ca6e4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,7 +26,7 @@ ], "lib": [ "ESNext" - ] + ], }, "include": [ "src/**/*.ts" @@ -34,5 +34,9 @@ "exclude": [ "node_modules", "**/*.test.ts" - ] + ], + "ts-node": { + "esm": true + }, + "files": ["./custom.d.ts"] }