From 80b9979ef471b380b44b4dfbbbdea492106e6e77 Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Sat, 1 Jun 2024 21:37:40 +0500 Subject: [PATCH 1/9] Add mongoose, setup db --- docker-compose.dev.yml | 28 ++ package-lock.json | 322 +++++++++++++++++- package.json | 1 + src/main.rest.ts | 2 + src/rest/rest.application.ts | 21 +- src/shared/helpers/database.ts | 9 + src/shared/helpers/index.ts | 1 + src/shared/libs/config/rest.schema.ts | 28 ++ .../database-client.interface.ts | 4 + src/shared/libs/database-client/index.ts | 2 + .../database-client/mongo.database-client.ts | 45 +++ src/shared/types/component.enum.ts | 1 + 12 files changed, 454 insertions(+), 10 deletions(-) create mode 100644 docker-compose.dev.yml create mode 100644 src/shared/helpers/database.ts create mode 100644 src/shared/libs/database-client/database-client.interface.ts create mode 100644 src/shared/libs/database-client/index.ts create mode 100644 src/shared/libs/database-client/mongo.database-client.ts diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..54bb83b --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,28 @@ +services: + db: + image: mongo:4.2 + restart: always + container_name: six-cities_mongo_db + environment: + MONGO_INITDB_ROOT_USERNAME: ${DB_USER} + MONGO_INITDB_ROOT_PASSWORD: ${DB_PASSWORD} + ports: + - ${DB_PORT}:27017 + volumes: + - six-cities_data:/data/db + + db_ui: + image: mongo-express:1.0.2-20 + restart: always + container_name: six-cities_mongo_express + ports: + - 8081:8081 + environment: + ME_CONFIG_BASICAUTH_USERNAME: ${DB_USER} + ME_CONFIG_BASICAUTH_PASSWORD: ${DB_PASSWORD} + ME_CONFIG_MONGODB_ADMINUSERNAME: ${DB_USER} + ME_CONFIG_MONGODB_ADMINPASSWORD: ${DB_PASSWORD} + ME_CONFIG_MONGODB_URL: mongodb://${DB_USER}:${DB_PASSWORD}@db:${DB_PORT}/ + +volumes: + six-cities_data: diff --git a/package-lock.json b/package-lock.json index ea0b60a..5627a2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "dotenv": "16.4.5", "got": "14.2.1", "inversify": "^6.0.2", + "mongoose": "8.3.4", "pino": "9.1.0", "pino-pretty": "11.1.0", "reflect-metadata": "^0.2.2", @@ -323,6 +324,14 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz", + "integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==", + "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", @@ -470,6 +479,19 @@ "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", "dev": true }, + "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", @@ -1028,6 +1050,14 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz", + "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -1523,7 +1553,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" }, @@ -3885,6 +3914,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", @@ -4066,6 +4103,11 @@ "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.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -4215,6 +4257,86 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/mongodb": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", + "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.4.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.3.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.3.4.tgz", + "integrity": "sha512-ckBaBzKgtWgCalW/LPkcBsR3wKCOYEJ9jLFPmYCYV7TLStpETY757ELx8/1stL11+6HxLLVffawBffXzd0Y7YA==", + "dependencies": { + "bson": "^6.5.0", + "kareem": "2.6.3", + "mongodb": "6.5.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/morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -4258,11 +4380,29 @@ "node": ">= 0.8" } }, + "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/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/nanoid": { "version": "3.3.7", @@ -4903,7 +5043,6 @@ "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" } @@ -5513,6 +5652,11 @@ "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", @@ -5542,6 +5686,14 @@ "atomic-sleep": "^1.0.0" } }, + "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", @@ -5942,6 +6094,17 @@ "node": ">=0.6" } }, + "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", @@ -6227,6 +6390,26 @@ "node": ">=0.10.48" } }, + "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", @@ -6683,6 +6866,14 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "@mongodb-js/saslprep": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz", + "integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==", + "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", @@ -6809,6 +7000,19 @@ "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", "dev": true }, + "@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", @@ -7178,6 +7382,11 @@ "fill-range": "^7.0.1" } }, + "bson": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz", + "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==" + }, "buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -7534,7 +7743,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" } @@ -9244,6 +9452,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", @@ -9385,6 +9598,11 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "dev": true }, + "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.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -9494,6 +9712,46 @@ "integrity": "sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==", "dev": true }, + "mongodb": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.5.0.tgz", + "integrity": "sha512-Fozq68InT+JKABGLqctgtb8P56pRrJFkbhW0ux+x1mdHeyinor8oNzJqwLjV/t5X5nJGfTlluxfyMnOXNggIUA==", + "requires": { + "@mongodb-js/saslprep": "^1.1.5", + "bson": "^6.4.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.3.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.3.4.tgz", + "integrity": "sha512-ckBaBzKgtWgCalW/LPkcBsR3wKCOYEJ9jLFPmYCYV7TLStpETY757ELx8/1stL11+6HxLLVffawBffXzd0Y7YA==", + "requires": { + "bson": "^6.5.0", + "kareem": "2.6.3", + "mongodb": "6.5.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==" + } + } + }, "morgan": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", @@ -9533,11 +9791,23 @@ } } }, + "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" + } + }, "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==" }, "nanoid": { "version": "3.3.7", @@ -10009,8 +10279,7 @@ "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.11.0", @@ -10451,6 +10720,11 @@ "object-inspect": "^1.9.0" } }, + "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", @@ -10471,6 +10745,14 @@ "atomic-sleep": "^1.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", @@ -10746,6 +11028,14 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "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", @@ -10946,6 +11236,20 @@ "integrity": "sha512-D8d+YxCUpoqtCnQzDxm6SF7DLU3gr2535T4khAtMq4osBahsQnmSxuwXFdrbAdDGG8Uokzfis/jvyeFPdmlc7w==", "dev": true }, + "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", diff --git a/package.json b/package.json index ba08bda..5f7a4d6 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "dotenv": "16.4.5", "got": "14.2.1", "inversify": "^6.0.2", + "mongoose": "8.3.4", "pino": "9.1.0", "pino-pretty": "11.1.0", "reflect-metadata": "^0.2.2", diff --git a/src/main.rest.ts b/src/main.rest.ts index a26ff8e..7b2bb62 100644 --- a/src/main.rest.ts +++ b/src/main.rest.ts @@ -5,12 +5,14 @@ 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 { Component } from './shared/types/index.js'; +import { MongoDatabaseClient, DatabaseClient } from './shared/libs/database-client/index.js'; async function bootstrap() { 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(); const app = container.get(Component.RestApplication); await app.init(); diff --git a/src/rest/rest.application.ts b/src/rest/rest.application.ts index dff8b60..d47ec38 100644 --- a/src/rest/rest.application.ts +++ b/src/rest/rest.application.ts @@ -3,16 +3,35 @@ import { Config } from '../shared/libs/config/index.js'; import { 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/database-client.interface.js'; +import { getMongoURI } from '../shared/helpers/database.js'; @injectable() export class RestApplication { 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 ) {} + private async initDb() { + const mongoUri = getMongoURI( + this.config.get('DB_USER'), + 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); + } + public async init() { this.logger.info('Application initialization'); 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'); } } diff --git a/src/shared/helpers/database.ts b/src/shared/helpers/database.ts new file mode 100644 index 0000000..f79b628 --- /dev/null +++ b/src/shared/helpers/database.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/index.ts b/src/shared/helpers/index.ts index d62b790..110a69b 100644 --- a/src/shared/helpers/index.ts +++ b/src/shared/helpers/index.ts @@ -6,3 +6,4 @@ export { } from './common.js'; export { getCurrentModuleDirectoryPath } from './file-system.js'; +export { getMongoURI } from './database.js'; diff --git a/src/shared/libs/config/rest.schema.ts b/src/shared/libs/config/rest.schema.ts index 2fda343..4f4f468 100644 --- a/src/shared/libs/config/rest.schema.ts +++ b/src/shared/libs/config/rest.schema.ts @@ -7,6 +7,10 @@ export type RestSchema = { PORT: number; SALT: string; DB_HOST: string; + DB_USER: string; + DB_PASSWORD: string; + DB_PORT: string; + DB_NAME: string; }; export const configRestSchema = convict({ @@ -28,4 +32,28 @@ export const configRestSchema = convict({ env: 'DB_HOST', default: '127.0.0.1', }, + DB_USER: { + doc: 'Username to connect to the database', + format: String, + env: 'DB_USER', + 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', + }, + DB_NAME: { + doc: 'Database name (MongoDB)', + format: String, + env: 'DB_NAME', + default: 'six-cities', + }, }); 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..cd67e28 --- /dev/null +++ b/src/shared/libs/database-client/mongo.database-client.ts @@ -0,0 +1,45 @@ +import * as Mongoose from 'mongoose'; +import { DatabaseClient } from './database-client.interface.js'; +import { Logger } from '../logger/logger.interface.js'; +import { injectable, inject } from 'inversify'; +import { Component } from '../../types/component.enum.js'; + +@injectable() +export class MongoDatabaseClient implements DatabaseClient { + private mongoose: typeof Mongoose; + private isConnected: boolean; + + constructor( + @inject(Component.Logger) private readonly logger: Logger + ) { + this.isConnected = false; + } + + public isConnectedToDatabase() { + return this.isConnected; + } + + public async connect(uri: string): Promise { + if (this.isConnectedToDatabase()) { + throw new Error('MongoDB client already connected'); + } + + this.logger.info('Trying to connect to MongoDB…'); + + this.mongoose = await Mongoose.connect(uri); + this.isConnected = true; + + this.logger.info('Database connection established.'); + } + + public async disconnect(): Promise { + if (!this.isConnectedToDatabase()) { + throw new Error('Not connected to the database'); + } + + await this.mongoose.disconnect?.(); + this.isConnected = false; + this.logger.info('Database connection closed.'); + } + +} diff --git a/src/shared/types/component.enum.ts b/src/shared/types/component.enum.ts index 1af415e..263c159 100644 --- a/src/shared/types/component.enum.ts +++ b/src/shared/types/component.enum.ts @@ -2,4 +2,5 @@ export const Component = { RestApplication: Symbol.for('RestApplication'), Config: Symbol.for('Config'), Logger: Symbol.for('Logger'), + DatabaseClient: Symbol.for('DatabaseClient'), } as const; From 0bba5856cbc1ef808598f92d6bf6e1a042109981 Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Sat, 1 Jun 2024 22:43:41 +0500 Subject: [PATCH 2/9] Add retry connection on error --- .../database-client/mongo.database-client.ts | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/shared/libs/database-client/mongo.database-client.ts b/src/shared/libs/database-client/mongo.database-client.ts index cd67e28..747355a 100644 --- a/src/shared/libs/database-client/mongo.database-client.ts +++ b/src/shared/libs/database-client/mongo.database-client.ts @@ -1,9 +1,14 @@ import * as Mongoose from 'mongoose'; +import { injectable, inject } from 'inversify'; +import { setTimeout } from 'node:timers/promises'; + import { DatabaseClient } from './database-client.interface.js'; import { Logger } from '../logger/logger.interface.js'; -import { injectable, inject } from 'inversify'; import { Component } from '../../types/component.enum.js'; +const RETRY_COUNT = 5; +const RETRY_TIMEOUT = 1000; + @injectable() export class MongoDatabaseClient implements DatabaseClient { private mongoose: typeof Mongoose; @@ -26,10 +31,21 @@ export class MongoDatabaseClient implements DatabaseClient { this.logger.info('Trying to connect to MongoDB…'); - this.mongoose = await Mongoose.connect(uri); - this.isConnected = true; + let attempt = 0; + while (attempt < RETRY_COUNT) { + try { + this.mongoose = await Mongoose.connect(uri); + this.isConnected = true; + this.logger.info('Database connection established.'); + return; + } catch (error) { + attempt += 1; + this.logger.error(`Failed to connect to the database. Attempt ${attempt}`, error as Error); + await setTimeout(RETRY_TIMEOUT); + } + } - this.logger.info('Database connection established.'); + throw new Error(`Unable to establish database connection after ${RETRY_COUNT}`); } public async disconnect(): Promise { From a52628b553d3cc76300f77641bf8e32b189955c0 Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Sun, 2 Jun 2024 17:46:01 +0500 Subject: [PATCH 3/9] Add typegoose, add user entity --- package-lock.json | 107 +++++++++--------- package.json | 1 + src/shared/helpers/hash.ts | 4 + .../modules/user/default-user.service.ts | 22 ++++ .../modules/user/dto/create-user.dto.ts | 9 ++ src/shared/modules/user/index.ts | 3 + .../modules/user/user-service.interface.ts | 10 ++ src/shared/modules/user/user.entity.ts | 50 ++++++++ src/shared/types/user.type.ts | 7 +- 9 files changed, 160 insertions(+), 53 deletions(-) create mode 100644 src/shared/helpers/hash.ts create mode 100644 src/shared/modules/user/default-user.service.ts create mode 100644 src/shared/modules/user/dto/create-user.dto.ts create mode 100644 src/shared/modules/user/index.ts create mode 100644 src/shared/modules/user/user-service.interface.ts create mode 100644 src/shared/modules/user/user.entity.ts diff --git a/package-lock.json b/package-lock.json index 5627a2c..5d45a95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "six-cities", "version": "7.0.0", "dependencies": { + "@typegoose/typegoose": "12.4.0", "chalk": "5.3.0", "convict": "6.2.4", "convict-format-with-validator": "6.2.0", @@ -423,6 +424,24 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "dev": true }, + "node_modules/@typegoose/typegoose": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/@typegoose/typegoose/-/typegoose-12.4.0.tgz", + "integrity": "sha512-KlTE9zqTFdt8q7m64ETcO7nk8idiY0EtxAR8m8DF9mZIwiQyp/KlJeT5sBdEo+wkzRO12VUmoqZm86qacz5m/Q==", + "dependencies": { + "lodash": "^4.17.20", + "loglevel": "^1.9.1", + "reflect-metadata": "^0.2.2", + "semver": "^7.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "mongoose": "~8.3.1" + } + }, "node_modules/@types/convict": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/@types/convict/-/convict-6.1.6.tgz", @@ -4014,8 +4033,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-id": { "version": "0.14.1", @@ -4037,6 +4055,18 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==", + "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", @@ -4076,18 +4106,6 @@ "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", @@ -5493,13 +5511,9 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" }, "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, - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "bin": { "semver": "bin/semver.js" }, @@ -6577,12 +6591,6 @@ "node": ">=10" } }, - "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/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -6944,6 +6952,18 @@ "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", "dev": true }, + "@typegoose/typegoose": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/@typegoose/typegoose/-/typegoose-12.4.0.tgz", + "integrity": "sha512-KlTE9zqTFdt8q7m64ETcO7nk8idiY0EtxAR8m8DF9mZIwiQyp/KlJeT5sBdEo+wkzRO12VUmoqZm86qacz5m/Q==", + "requires": { + "lodash": "^4.17.20", + "loglevel": "^1.9.1", + "reflect-metadata": "^0.2.2", + "semver": "^7.6.0", + "tslib": "^2.6.2" + } + }, "@types/convict": { "version": "6.1.6", "resolved": "https://registry.npmjs.org/@types/convict/-/convict-6.1.6.tgz", @@ -9530,8 +9550,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-id": { "version": "0.14.1", @@ -9550,6 +9569,11 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "loglevel": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.1.tgz", + "integrity": "sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg==" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -9577,15 +9601,6 @@ "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", @@ -10584,13 +10599,9 @@ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==" }, "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, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==" }, "semver-compare": { "version": "1.0.0", @@ -11366,12 +11377,6 @@ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "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 - }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 5f7a4d6..246b30d 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "npm": ">=10" }, "dependencies": { + "@typegoose/typegoose": "12.4.0", "chalk": "5.3.0", "convict": "6.2.4", "convict-format-with-validator": "6.2.0", diff --git a/src/shared/helpers/hash.ts b/src/shared/helpers/hash.ts new file mode 100644 index 0000000..f8ee429 --- /dev/null +++ b/src/shared/helpers/hash.ts @@ -0,0 +1,4 @@ +import { createHmac } from 'node:crypto'; + +export const createSHA256 = (line: string, salt: string): string => + createHmac('sha256', salt).update(line).digest('hex'); 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..a3288b9 --- /dev/null +++ b/src/shared/modules/user/default-user.service.ts @@ -0,0 +1,22 @@ +import { DocumentType } from '@typegoose/typegoose'; + +import { CreateUserDto } from './dto/create-user.dto.js'; +import { UserService } from './user-service.interface.js'; +import { UserEntity, UserModel } from './user.entity.js'; + +export class DefaultUserService implements UserService { + findByEmail(email: string): Promise> { + throw new Error('Method not implemented.'); + } + + findOrCreate(dto: CreateUserDto, salt: string): Promise> { + throw new Error('Method not implemented.'); + } + + public async create(dto: CreateUserDto, salt: string): Promise> { + const user = new UserEntity(dto); + user.setPassword(dto.password, salt); + + return UserModel.create(user); + } +} 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..60ad074 --- /dev/null +++ b/src/shared/modules/user/dto/create-user.dto.ts @@ -0,0 +1,9 @@ +import { UserType } from '../../../types/index.js'; + +export class CreateUserDto { + public name: string; + public email: string; + public avatarPath: string; + public password: string; + public type: UserType; +} diff --git a/src/shared/modules/user/index.ts b/src/shared/modules/user/index.ts new file mode 100644 index 0000000..92ea356 --- /dev/null +++ b/src/shared/modules/user/index.ts @@ -0,0 +1,3 @@ +export { UserEntity, UserModel } from './user.entity.js'; +export { CreateUserDto } from './dto/create-user.dto.js'; +export { DefaultUserService } from './default-user.service.js'; 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..1afec3d --- /dev/null +++ b/src/shared/modules/user/user-service.interface.ts @@ -0,0 +1,10 @@ +import { DocumentType } from '@typegoose/typegoose'; + +import { UserEntity } from './user.entity.js'; +import { CreateUserDto } from './dto/create-user.dto.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.entity.ts b/src/shared/modules/user/user.entity.ts new file mode 100644 index 0000000..457f745 --- /dev/null +++ b/src/shared/modules/user/user.entity.ts @@ -0,0 +1,50 @@ +import { defaultClasses, getModelForClass, modelOptions, prop } from '@typegoose/typegoose'; +import { User, UserType } from '../../types/index.js'; +import { createSHA256 } from '../../helpers/hash.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, default: '' }) + public name: string; + + @prop({ unique: true, required: true }) + public email: string; + + @prop({ required: false, default: '' }) + public avatarPath: string; + + @prop({ required: true, default: '' }) + public password: string; + + @prop({ required: true, enum: UserType, default: UserType.Common }) + public type: UserType; + + constructor(userData: User) { + super(); + + this.name = userData.name; + this.email = userData.email; + this.avatarPath = userData.avatarPath; + this.password = userData.password; + this.type = userData.type; + } + + public setPassword(password: string, salt: string) { + this.password = createSHA256(password, salt); + } + + public getPassword() { + return this.password; + } +} + +export const UserModel = getModelForClass(UserEntity); diff --git a/src/shared/types/user.type.ts b/src/shared/types/user.type.ts index 446456d..68366d9 100644 --- a/src/shared/types/user.type.ts +++ b/src/shared/types/user.type.ts @@ -1,9 +1,12 @@ -export type UserType = 'common' | 'pro'; +export enum UserType { + Common = 'Common', + Pro = 'Pro', +} export type User = { name: string, email: string, - avatarPath?: string, + avatarPath: string, password: string, type: UserType, } From 046b5cb27377cf6883e31266b8cf1a5ccff01792 Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Mon, 3 Jun 2024 11:52:22 +0500 Subject: [PATCH 4/9] Update UserService, Separate app containers --- src/main.rest.ts | 16 ++++---- src/rest/index.ts | 1 + src/rest/rest.container.ts | 28 ++++++++++++++ .../modules/user/default-user.service.ts | 38 ++++++++++++++----- src/shared/modules/user/index.ts | 1 + src/shared/modules/user/user.container.ts | 19 ++++++++++ src/shared/types/component.enum.ts | 2 + 7 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 src/rest/rest.container.ts create mode 100644 src/shared/modules/user/user.container.ts diff --git a/src/main.rest.ts b/src/main.rest.ts index 7b2bb62..d7fb968 100644 --- a/src/main.rest.ts +++ b/src/main.rest.ts @@ -2,19 +2,17 @@ import 'reflect-metadata'; import { Container } from 'inversify'; 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 { Component } from './shared/types/index.js'; -import { MongoDatabaseClient, DatabaseClient } from './shared/libs/database-client/index.js'; +import { createUserContainer } from './shared/modules/user/index.js'; +import { createRestApplicationContainer } from './rest/index.js'; async function bootstrap() { - 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(); + const appContainer = Container.merge( + createRestApplicationContainer(), + createUserContainer() + ); - const app = container.get(Component.RestApplication); + const app = appContainer.get(Component.RestApplication); await app.init(); } diff --git a/src/rest/index.ts b/src/rest/index.ts index 5e51a1d..00cd32a 100644 --- a/src/rest/index.ts +++ b/src/rest/index.ts @@ -1 +1,2 @@ export { RestApplication } from './rest.application.js'; +export { createRestApplicationContainer } from './rest.container.js'; diff --git a/src/rest/rest.container.ts b/src/rest/rest.container.ts new file mode 100644 index 0000000..fbe1c6b --- /dev/null +++ b/src/rest/rest.container.ts @@ -0,0 +1,28 @@ +import { Container } from 'inversify'; +import { Component } from '../shared/types/index.js'; +import { RestApplication } from './rest.application.js'; +import { Logger, PinoLogger } from '../shared/libs/logger/index.js'; +import { Config, RestConfig, RestSchema } from '../shared/libs/config/index.js'; +import { DatabaseClient, MongoDatabaseClient } from '../shared/libs/database-client/index.js'; + +export function createRestApplicationContainer() { + const restApplicationContainer = new Container(); + + restApplicationContainer + .bind(Component.RestApplication).to(RestApplication) + .inSingletonScope(); + + restApplicationContainer + .bind(Component.Logger).to(PinoLogger) + .inSingletonScope(); + + restApplicationContainer + .bind>(Component.Config).to(RestConfig) + .inSingletonScope(); + + restApplicationContainer + .bind(Component.DatabaseClient).to(MongoDatabaseClient) + .inSingletonScope(); + + return restApplicationContainer; +} diff --git a/src/shared/modules/user/default-user.service.ts b/src/shared/modules/user/default-user.service.ts index a3288b9..7a7773d 100644 --- a/src/shared/modules/user/default-user.service.ts +++ b/src/shared/modules/user/default-user.service.ts @@ -1,22 +1,40 @@ -import { DocumentType } from '@typegoose/typegoose'; +import { DocumentType, types } from '@typegoose/typegoose'; +import { inject, injectable } from 'inversify'; import { CreateUserDto } from './dto/create-user.dto.js'; import { UserService } from './user-service.interface.js'; -import { UserEntity, UserModel } from './user.entity.js'; +import { UserEntity } from './user.entity.js'; +import { Logger } from '../../libs/logger/index.js'; +import { Component } from '../../types/component.enum.js'; +injectable(); export class DefaultUserService implements UserService { - findByEmail(email: string): Promise> { - throw new Error('Method not implemented.'); - } - - findOrCreate(dto: CreateUserDto, salt: string): Promise> { - throw new Error('Method not implemented.'); - } + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.UserModel) private readonly userModel: types.ModelType + ) {} public async create(dto: CreateUserDto, salt: string): Promise> { const user = new UserEntity(dto); user.setPassword(dto.password, salt); - return UserModel.create(user); + 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/index.ts b/src/shared/modules/user/index.ts index 92ea356..24a5987 100644 --- a/src/shared/modules/user/index.ts +++ b/src/shared/modules/user/index.ts @@ -1,3 +1,4 @@ export { UserEntity, UserModel } from './user.entity.js'; export { CreateUserDto } from './dto/create-user.dto.js'; export { DefaultUserService } from './default-user.service.js'; +export { createUserContainer } from './user.container.js'; diff --git a/src/shared/modules/user/user.container.ts b/src/shared/modules/user/user.container.ts new file mode 100644 index 0000000..43a8f1d --- /dev/null +++ b/src/shared/modules/user/user.container.ts @@ -0,0 +1,19 @@ +import { Container } from 'inversify'; + +import { Component } from '../../types/index.js'; +import { DefaultUserService } from './default-user.service.js'; +import { UserService } from './user-service.interface.js'; +import { UserEntity, UserModel } from './user.entity.js'; +import { types } from '@typegoose/typegoose'; + +export function createUserContainer() { + const userContainer = new Container(); + + userContainer + .bind(Component.UserService).to(DefaultUserService) + .inSingletonScope(); + + userContainer.bind>(Component.UserModel).toConstantValue(UserModel); + + return userContainer; +} diff --git a/src/shared/types/component.enum.ts b/src/shared/types/component.enum.ts index 263c159..a65ab61 100644 --- a/src/shared/types/component.enum.ts +++ b/src/shared/types/component.enum.ts @@ -3,4 +3,6 @@ export const Component = { Config: Symbol.for('Config'), Logger: Symbol.for('Logger'), DatabaseClient: Symbol.for('DatabaseClient'), + UserService: Symbol.for('UserService'), + UserModel: Symbol.for('UserModel'), } as const; From faf64dca50373997252f17f134c058322f67eb18 Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Mon, 3 Jun 2024 12:25:17 +0500 Subject: [PATCH 5/9] Add console logger --- src/shared/libs/logger/console.logger.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/shared/libs/logger/console.logger.ts diff --git a/src/shared/libs/logger/console.logger.ts b/src/shared/libs/logger/console.logger.ts new file mode 100644 index 0000000..b2b9f4b --- /dev/null +++ b/src/shared/libs/logger/console.logger.ts @@ -0,0 +1,21 @@ +import { Logger } from './logger.interface.js'; +import { getErrorMessage } from '../../helpers/index.js'; + +export class ConsoleLogger implements Logger { + info(message: string, ...args: unknown[]): void { + console.info(message, args); + } + + warn(message: string, ...args: unknown[]): void { + console.warn(message, args); + } + + error(message: string, error: Error, ...args: unknown[]): void { + console.error(message, args); + console.error(`Error message: ${getErrorMessage(error)}`); + } + + debug(message: string, ...args: unknown[]): void { + console.debug(message, args); + } +} From 39c8490860bb248068656a348f2e69f9dafe60d3 Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Tue, 4 Jun 2024 10:17:00 +0500 Subject: [PATCH 6/9] Update import command --- src/cli/commands/command.constant.ts | 2 ++ src/cli/commands/import.command.ts | 34 +++++++++++++++---- src/main.cli.ts | 2 ++ .../libs/file-reader/tsv-file-reader.ts | 5 ++- src/shared/libs/logger/index.ts | 1 + src/shared/modules/user/index.ts | 1 + 6 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 src/cli/commands/command.constant.ts diff --git a/src/cli/commands/command.constant.ts b/src/cli/commands/command.constant.ts new file mode 100644 index 0000000..e4f02f8 --- /dev/null +++ b/src/cli/commands/command.constant.ts @@ -0,0 +1,2 @@ +export const DEFAULT_DB_PORT = '27017'; +export const DEFAULT_USER_PASSWORD = 'admin'; diff --git a/src/cli/commands/import.command.ts b/src/cli/commands/import.command.ts index 1ae188d..d711189 100644 --- a/src/cli/commands/import.command.ts +++ b/src/cli/commands/import.command.ts @@ -1,25 +1,47 @@ import { Command } from './command.interface.js'; import { TSVFileReader } from '../../shared/libs/file-reader/index.js'; import { Offer } from '../../shared/types/offer.type.js'; -import { getErrorMessage } from '../../shared/helpers/common.js'; +import { getErrorMessage, getMongoURI } from '../../shared/helpers/index.js'; +import { DefaultUserService, UserModel, UserService } from '../../shared/modules/user/index.js'; +import { DatabaseClient, MongoDatabaseClient } from '../../shared/libs/database-client/index.js'; +import { Logger, ConsoleLogger } from '../../shared/libs/logger/index.js'; +import { DEFAULT_DB_PORT } from './command.constant.js'; export class ImportCommand implements Command { - private onImportedOffer(offer: Offer): void { - console.info(offer); + private userService: UserService; + 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.userService = new DefaultUserService(this.logger, UserModel); + this.databaseClient = new MongoDatabaseClient(this.logger); + } + + private async onImportedOffer(offer: Offer, resolve: () => void) { + resolve(); } private onCompleteImport(count: number) { console.info(`${count} rows imported.`); + this.databaseClient.disconnect(); } public getName(): string { return '--import'; } - public async execute(...parameters: string[]): Promise { - const [filename] = parameters; - const fileReader = new TSVFileReader(filename.trim()); + 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); fileReader.on('end', this.onCompleteImport); diff --git a/src/main.cli.ts b/src/main.cli.ts index b377e46..a2afaff 100644 --- a/src/main.cli.ts +++ b/src/main.cli.ts @@ -1,4 +1,6 @@ #!/usr/bin/env node +import 'reflect-metadata'; + import { CLIApplication, GenerateCommand, HelpCommand, ImportCommand, VersionCommand } from './cli/index.js'; function bootstrap() { diff --git a/src/shared/libs/file-reader/tsv-file-reader.ts b/src/shared/libs/file-reader/tsv-file-reader.ts index d046bff..b40d5ed 100644 --- a/src/shared/libs/file-reader/tsv-file-reader.ts +++ b/src/shared/libs/file-reader/tsv-file-reader.ts @@ -94,7 +94,10 @@ export class TSVFileReader extends EventEmitter implements FileReader { importedRowCount += 1; const parserOffer = this.parseLineToOffer(completeRow); - this.emit('line', parserOffer); + + await new Promise((resolve) => { + this.emit('line', parserOffer, resolve); + }); } } diff --git a/src/shared/libs/logger/index.ts b/src/shared/libs/logger/index.ts index 59abf8f..2af0aff 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/modules/user/index.ts b/src/shared/modules/user/index.ts index 24a5987..3a42be6 100644 --- a/src/shared/modules/user/index.ts +++ b/src/shared/modules/user/index.ts @@ -2,3 +2,4 @@ export { UserEntity, UserModel } from './user.entity.js'; export { CreateUserDto } from './dto/create-user.dto.js'; export { DefaultUserService } from './default-user.service.js'; export { createUserContainer } from './user.container.js'; +export { UserService } from './user-service.interface.js'; From 12586e8a78c6b95f3d2e6dfa55a256d0caf48b36 Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Sat, 8 Jun 2024 22:18:31 +0500 Subject: [PATCH 7/9] Refactoring types --- src/cli/commands/import.command.ts | 2 +- .../libs/file-reader/tsv-file-reader.ts | 15 ++++-- .../offer-generator/tsv-offer-generator.ts | 7 +-- src/shared/modules/user/user.entity.ts | 4 +- src/shared/types/index.ts | 14 +++++- src/shared/types/offer.type.ts | 46 ------------------- src/shared/types/offer/amenities.type.ts | 1 + src/shared/types/offer/cities.enum.ts | 10 ++++ .../types/offer/coordinates-cities.const.ts | 11 +++++ src/shared/types/offer/coordinates.type.ts | 4 ++ src/shared/types/offer/index.ts | 7 +++ src/shared/types/offer/offer.type.ts | 25 ++++++++++ src/shared/types/offer/photos.type.ts | 1 + src/shared/types/offer/property-type.type.ts | 1 + src/shared/types/user/index.ts | 2 + src/shared/types/user/user-type.enum.ts | 4 ++ src/shared/types/{ => user}/user.type.ts | 7 +-- 17 files changed, 99 insertions(+), 62 deletions(-) delete mode 100644 src/shared/types/offer.type.ts create mode 100644 src/shared/types/offer/amenities.type.ts create mode 100644 src/shared/types/offer/cities.enum.ts create mode 100644 src/shared/types/offer/coordinates-cities.const.ts create mode 100644 src/shared/types/offer/coordinates.type.ts create mode 100644 src/shared/types/offer/index.ts create mode 100644 src/shared/types/offer/offer.type.ts create mode 100644 src/shared/types/offer/photos.type.ts create mode 100644 src/shared/types/offer/property-type.type.ts create mode 100644 src/shared/types/user/index.ts create mode 100644 src/shared/types/user/user-type.enum.ts rename src/shared/types/{ => user}/user.type.ts (53%) diff --git a/src/cli/commands/import.command.ts b/src/cli/commands/import.command.ts index d711189..52217e6 100644 --- a/src/cli/commands/import.command.ts +++ b/src/cli/commands/import.command.ts @@ -1,6 +1,6 @@ import { Command } from './command.interface.js'; import { TSVFileReader } from '../../shared/libs/file-reader/index.js'; -import { Offer } from '../../shared/types/offer.type.js'; +import { Offer } from '../../shared/types/index.js'; import { getErrorMessage, getMongoURI } from '../../shared/helpers/index.js'; import { DefaultUserService, UserModel, UserService } from '../../shared/modules/user/index.js'; import { DatabaseClient, MongoDatabaseClient } from '../../shared/libs/database-client/index.js'; diff --git a/src/shared/libs/file-reader/tsv-file-reader.ts b/src/shared/libs/file-reader/tsv-file-reader.ts index b40d5ed..f555e8b 100644 --- a/src/shared/libs/file-reader/tsv-file-reader.ts +++ b/src/shared/libs/file-reader/tsv-file-reader.ts @@ -2,7 +2,16 @@ import EventEmitter from 'node:events'; import { createReadStream } from 'node:fs'; import { FileReader } from './file-reader.interface.js'; -import { Offer, User, UserType, PropertyType, Amenities, Coordinates } from '../../types/index.js'; +import { + User, + Amenities, + City, + Offer, + Photos, + PropertyType, + Coordinates, + UserType +} from '../../types/index.js'; export class TSVFileReader extends EventEmitter implements FileReader { private CHUNK_SIZE = 16384; // 16KB @@ -42,9 +51,9 @@ export class TSVFileReader extends EventEmitter implements FileReader { title, description, postDate: new Date(postDate), - city, + city: city as City, preview, - photos: photos.split(';'), + photos: photos.split(';') as Photos, isPremium: Boolean(Number.parseInt(isPremium, 10)), isFavorite: Boolean(Number.parseInt(isFavorite, 10)), rating: Number.parseInt(rating, 10), diff --git a/src/shared/libs/offer-generator/tsv-offer-generator.ts b/src/shared/libs/offer-generator/tsv-offer-generator.ts index 9210168..633d6b9 100644 --- a/src/shared/libs/offer-generator/tsv-offer-generator.ts +++ b/src/shared/libs/offer-generator/tsv-offer-generator.ts @@ -5,9 +5,10 @@ import { MockServerData } from '../../types/mock-server-data.type.js'; import { generateRandomValue, getRandomItem, - getRandomItems + getRandomItems, } from '../../helpers/index.js'; -import { CitiesEnum, CoordinatesCities } from '../../types/offer.type.js'; + +import { CoordinatesCities } from '../../types/index.js'; const MIN_PRICE = 100; const MAX_PRICE = 100000; @@ -34,7 +35,7 @@ export class TSVOfferGenerator implements OfferGenerator { .subtract(generateRandomValue(FIRST_WEEK_DAY, LAST_WEEK_DAY), 'day') .toISOString(); - const city: CitiesEnum = getRandomItem(this.mockData.cities) as CitiesEnum; + const city = getRandomItem(this.mockData.cities); const preview = 'preview.jpg'; const photos = this.mockData.photos.join(';'); const isPremium = generateRandomValue(0, 1); diff --git a/src/shared/modules/user/user.entity.ts b/src/shared/modules/user/user.entity.ts index 457f745..7f66e82 100644 --- a/src/shared/modules/user/user.entity.ts +++ b/src/shared/modules/user/user.entity.ts @@ -20,12 +20,12 @@ export class UserEntity extends defaultClasses.TimeStamps implements User { public email: string; @prop({ required: false, default: '' }) - public avatarPath: string; + public avatarPath: string | undefined; @prop({ required: true, default: '' }) public password: string; - @prop({ required: true, enum: UserType, default: UserType.Common }) + @prop({ required: true, enum: UserType, default: UserType.Regular }) public type: UserType; constructor(userData: User) { diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts index f79e68c..803696e 100644 --- a/src/shared/types/index.ts +++ b/src/shared/types/index.ts @@ -1,4 +1,14 @@ -export { User, UserType } from './user.type.js'; -export { Offer, PropertyType, Amenities, Coordinates } from './offer.type.js'; +export { User, UserType } from './user/index.js'; +export { + Offer, + PropertyType, + Amenities, + CoordinatesCities, + Coordinates, + Cities, + City, + Photos, +} from './offer/index.js'; + export { MockServerData } from './mock-server-data.type.js'; export { Component } from './component.enum.js'; diff --git a/src/shared/types/offer.type.ts b/src/shared/types/offer.type.ts deleted file mode 100644 index e14347e..0000000 --- a/src/shared/types/offer.type.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { User } from './user.type.js'; - -export type PropertyType = 'apartment' | 'house' | 'room' | 'hotel'; -export type Amenities = 'Breakfast' | 'Air' | 'conditioning' | 'Laptop' | 'friendly' | 'workspace' | 'Baby seat' | 'Washer' | 'Towels' | 'Fridge'; - -export enum CitiesEnum { - Paris = 'Paris', - Cologne = 'Cologne', - Brussels = 'Brussels', - Amsterdam = 'Amsterdam', - Hamburg = 'Hamburg', - Dusseldorf = 'Dusseldorf', -} - -export type Coordinates = { - latitude: number, - longitude: number -}; - -export const CoordinatesCities: Record = { - [CitiesEnum.Paris]: { latitude: 48.85661, longitude: 2.351499 }, - [CitiesEnum.Cologne]: { latitude: 50.938361, longitude: 6.959974 }, - [CitiesEnum.Brussels]: { latitude: 50.846557, longitude: 4.351697 }, - [CitiesEnum.Amsterdam]: { latitude: 52.370216, longitude: 4.895168 }, - [CitiesEnum.Hamburg]: { latitude: 53.550341, longitude: 10.000654 }, - [CitiesEnum.Dusseldorf]: { latitude: 51.225402, longitude: 6.776314 }, -}; - -export type Offer = { - title: string, - description: string, - postDate: Date, - city: string, - preview: string, - photos: string[], - isPremium: boolean, - isFavorite: boolean, - rating: number, - type: PropertyType, - roomsCount: number, - guestsCount: number, - price: number, - amenities: Amenities[], - author: User, - coordinates: Coordinates, -} diff --git a/src/shared/types/offer/amenities.type.ts b/src/shared/types/offer/amenities.type.ts new file mode 100644 index 0000000..5be8a90 --- /dev/null +++ b/src/shared/types/offer/amenities.type.ts @@ -0,0 +1 @@ +export type Amenities = 'Breakfast' | 'Air' | 'conditioning' | 'Laptop' | 'friendly' | 'workspace' | 'Baby seat' | 'Washer' | 'Towels' | 'Fridge'; diff --git a/src/shared/types/offer/cities.enum.ts b/src/shared/types/offer/cities.enum.ts new file mode 100644 index 0000000..36ec0ed --- /dev/null +++ b/src/shared/types/offer/cities.enum.ts @@ -0,0 +1,10 @@ +export const Cities = { + Paris: 'Paris', + Cologne: 'Cologne', + Brussels: 'Brussels', + Amsterdam: 'Amsterdam', + Hamburg: 'Hamburg', + Dusseldorf: 'Dusseldorf', +} as const; + +export type City = keyof typeof Cities; diff --git a/src/shared/types/offer/coordinates-cities.const.ts b/src/shared/types/offer/coordinates-cities.const.ts new file mode 100644 index 0000000..eb66d3a --- /dev/null +++ b/src/shared/types/offer/coordinates-cities.const.ts @@ -0,0 +1,11 @@ +import { Cities, City } from './cities.enum.js'; +import { Coordinates } from './coordinates.type.js'; + +export const CoordinatesCities: Record = { + [Cities.Paris]: { latitude: 48.85661, longitude: 2.351499 }, + [Cities.Cologne]: { latitude: 50.938361, longitude: 6.959974 }, + [Cities.Brussels]: { latitude: 50.846557, longitude: 4.351697 }, + [Cities.Amsterdam]: { latitude: 52.370216, longitude: 4.895168 }, + [Cities.Hamburg]: { latitude: 53.550341, longitude: 10.000654 }, + [Cities.Dusseldorf]: { latitude: 51.225402, longitude: 6.776314 }, +}; diff --git a/src/shared/types/offer/coordinates.type.ts b/src/shared/types/offer/coordinates.type.ts new file mode 100644 index 0000000..e5c03ec --- /dev/null +++ b/src/shared/types/offer/coordinates.type.ts @@ -0,0 +1,4 @@ +export type Coordinates = { + latitude: number, + longitude: number +}; diff --git a/src/shared/types/offer/index.ts b/src/shared/types/offer/index.ts new file mode 100644 index 0000000..ddd2893 --- /dev/null +++ b/src/shared/types/offer/index.ts @@ -0,0 +1,7 @@ +export { Offer } from './offer.type.js'; +export { PropertyType } from './property-type.type.js'; +export { Amenities } from './amenities.type.js'; +export { Photos } from './photos.type.js'; +export { Cities, City } from './cities.enum.js'; +export { CoordinatesCities } from './coordinates-cities.const.js'; +export { Coordinates } from './coordinates.type.js'; diff --git a/src/shared/types/offer/offer.type.ts b/src/shared/types/offer/offer.type.ts new file mode 100644 index 0000000..1baa338 --- /dev/null +++ b/src/shared/types/offer/offer.type.ts @@ -0,0 +1,25 @@ +import { User } from '../user/index.js'; +import { Amenities } from './amenities.type.js'; +import { City } from './cities.enum.js'; +import { Coordinates } from './coordinates.type.js'; +import { Photos } from './photos.type.js'; +import { PropertyType } from './property-type.type.js'; + +export type Offer = { + title: string, + description: string, + postDate: Date, + city: City, + preview: string, + photos: Photos, + isPremium: boolean, + isFavorite: boolean, + rating: number, + type: PropertyType, + roomsCount: number, + guestsCount: number, + price: number, + amenities: Amenities[], + author: User, + coordinates: Coordinates, +} diff --git a/src/shared/types/offer/photos.type.ts b/src/shared/types/offer/photos.type.ts new file mode 100644 index 0000000..3961bc0 --- /dev/null +++ b/src/shared/types/offer/photos.type.ts @@ -0,0 +1 @@ +export type Photos = [string, string, string, string, string, string]; diff --git a/src/shared/types/offer/property-type.type.ts b/src/shared/types/offer/property-type.type.ts new file mode 100644 index 0000000..7b6a892 --- /dev/null +++ b/src/shared/types/offer/property-type.type.ts @@ -0,0 +1 @@ +export type PropertyType = 'apartment' | 'house' | 'room' | 'hotel'; diff --git a/src/shared/types/user/index.ts b/src/shared/types/user/index.ts new file mode 100644 index 0000000..c6847be --- /dev/null +++ b/src/shared/types/user/index.ts @@ -0,0 +1,2 @@ +export { UserType } from './user-type.enum.js'; +export { User } from './user.type.js'; diff --git a/src/shared/types/user/user-type.enum.ts b/src/shared/types/user/user-type.enum.ts new file mode 100644 index 0000000..8530d2a --- /dev/null +++ b/src/shared/types/user/user-type.enum.ts @@ -0,0 +1,4 @@ +export enum UserType { + Regular = 'Regular', + Pro = 'Pro', +} diff --git a/src/shared/types/user.type.ts b/src/shared/types/user/user.type.ts similarity index 53% rename from src/shared/types/user.type.ts rename to src/shared/types/user/user.type.ts index 68366d9..e6830fa 100644 --- a/src/shared/types/user.type.ts +++ b/src/shared/types/user/user.type.ts @@ -1,12 +1,9 @@ -export enum UserType { - Common = 'Common', - Pro = 'Pro', -} +import { UserType } from './user-type.enum.js'; export type User = { name: string, email: string, - avatarPath: string, + avatarPath?: string, password: string, type: UserType, } From d6e4b8f9527abf041ece0216caa2391f6027c1df Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Sun, 16 Jun 2024 02:40:06 +0500 Subject: [PATCH 8/9] Improve help command, clean up code --- docker-compose.dev.yml | 2 -- .../{mock-data.tsv => mock-data-example.tsv} | 0 queries.http | 4 ---- src/cli/commands/help.command.ts | 23 ++++++++++++------- 4 files changed, 15 insertions(+), 14 deletions(-) rename mocks/{mock-data.tsv => mock-data-example.tsv} (100%) delete mode 100644 queries.http diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 54bb83b..deb9d21 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,7 +1,6 @@ services: db: image: mongo:4.2 - restart: always container_name: six-cities_mongo_db environment: MONGO_INITDB_ROOT_USERNAME: ${DB_USER} @@ -13,7 +12,6 @@ services: db_ui: image: mongo-express:1.0.2-20 - restart: always container_name: six-cities_mongo_express ports: - 8081:8081 diff --git a/mocks/mock-data.tsv b/mocks/mock-data-example.tsv similarity index 100% rename from mocks/mock-data.tsv rename to mocks/mock-data-example.tsv diff --git a/queries.http b/queries.http deleted file mode 100644 index 4a73272..0000000 --- a/queries.http +++ /dev/null @@ -1,4 +0,0 @@ -# Get mock data from fake server -GET http://localhost:3123/api HTTP/1.1 - -### diff --git a/src/cli/commands/help.command.ts b/src/cli/commands/help.command.ts index 9ddf0d0..1d8e5ea 100644 --- a/src/cli/commands/help.command.ts +++ b/src/cli/commands/help.command.ts @@ -8,14 +8,21 @@ export class HelpCommand implements Command { public async execute(..._parameters: string[]): Promise { console.info(` - ${chalk.yellow('Программа для подготовки данных для REST API сервера.')} - Пример: - cli.js -- [--arguments] - Команды: - --version: # выводит номер версии - --help: # печатает этот текст - --import : # импортирует данные из TSV - --generate # генерирует произвольное количество тестовых данных + ${chalk.yellow('A program for preparing data for the REST API of the server.')} + + example: + cli.js -- [--arguments] + + Available commands: + ${chalk.green('--version')}: # output version + ${chalk.green('--help')}: # output help + ${chalk.green('--import ')}: # import data from tsv file, + - path to text file + + ${chalk.green('--generate ')}: # generates an arbitrary amount of text data. + the path where the new file will be created. + the server that the data will be generated from. + example: --generate 1 ./mocks/test-data.tsv http://localhost:3123/api `); } } From 064b9ba53f7fa81ae4e1ff41bf848944a4694978 Mon Sep 17 00:00:00 2001 From: evgenygorchakov Date: Sun, 16 Jun 2024 11:25:36 +0500 Subject: [PATCH 9/9] Add offer model, fix mocks --- mocks/mock-data-example.tsv | 2 +- mocks/mock-server-data.json | 6 +- src/cli/commands/generate.command.ts | 16 +++ src/cli/commands/import.command.ts | 43 ++++++- src/main.rest.ts | 6 +- .../libs/file-reader/tsv-file-reader.ts | 2 +- src/shared/libs/logger/console.logger.ts | 6 +- .../offer-generator/tsv-offer-generator.ts | 4 +- .../modules/offer/default-offer.service.ts | 26 ++++ .../modules/offer/dto/create-offer.dto.ts | 27 ++++ src/shared/modules/offer/index.ts | 5 + .../modules/offer/offer-service.interface.ts | 8 ++ src/shared/modules/offer/offer.container.ts | 15 +++ src/shared/modules/offer/offer.entity.ts | 118 ++++++++++++++++++ .../modules/user/dto/create-user.dto.ts | 2 +- src/shared/modules/user/user.entity.ts | 20 +-- src/shared/types/component.enum.ts | 2 + .../types/offer/coordinates-cities.const.ts | 2 +- src/shared/types/offer/offer.type.ts | 2 +- src/shared/types/user/user-type.enum.ts | 4 +- 20 files changed, 284 insertions(+), 32 deletions(-) create mode 100644 src/shared/modules/offer/default-offer.service.ts create mode 100644 src/shared/modules/offer/dto/create-offer.dto.ts create mode 100644 src/shared/modules/offer/index.ts create mode 100644 src/shared/modules/offer/offer-service.interface.ts create mode 100644 src/shared/modules/offer/offer.container.ts create mode 100644 src/shared/modules/offer/offer.entity.ts diff --git a/mocks/mock-data-example.tsv b/mocks/mock-data-example.tsv index 4449b63..ca32cc6 100644 --- a/mocks/mock-data-example.tsv +++ b/mocks/mock-data-example.tsv @@ -1 +1 @@ -Breathtaking Mountain views in cozy Birdbox Enjoy the relaxed and comfortable enclosure of the Birdbox. Sleep right beside nature and its amazing surroundings. 2022-04-06T08:45:40.283Z Dusseldorf preview.jpg photo1.jpg;photo2.jpg;photo3.jpg;photo4.jpg 0 1 5 house 2 4 2400 Laptop;friendly;workspace;Baby seat;Washer;Towels;Fridge John john@mail.com ava.jpg qwerty pro 48.85661;2.351499 +Breathtaking Mountain views in cozy Birdbox Enjoy the relaxed and comfortable enclosure of the Birdbox. Sleep right beside nature and its amazing surroundings. 2022-04-06T08:45:40.283Z Dusseldorf preview.jpg photo1.jpg;photo2.jpg;photo3.jpg;photo4.jpg;photo5.jpg;photo6.jpg 0 1 5 house 2 4 2400 Laptop;friendly;workspace;Baby seat;Washer;Towels;Fridge John john@mail.com ava.jpg qwerty pro 48.85661;2.351499 diff --git a/mocks/mock-server-data.json b/mocks/mock-server-data.json index 9728f51..1eb070a 100644 --- a/mocks/mock-server-data.json +++ b/mocks/mock-server-data.json @@ -28,7 +28,9 @@ "photo1.jpg", "photo2.jpg", "photo3.jpg", - "photo4.jpg" + "photo4.jpg", + "photo5.jpg", + "photo6.jpg" ], "types": [ "apartment", @@ -82,7 +84,7 @@ "qwerty2" ], "userTypes": [ - "common", + "regular", "pro" ] } diff --git a/src/cli/commands/generate.command.ts b/src/cli/commands/generate.command.ts index 9df2759..95cc17d 100644 --- a/src/cli/commands/generate.command.ts +++ b/src/cli/commands/generate.command.ts @@ -32,6 +32,22 @@ export class GenerateCommand implements Command { public async execute(...parameters: string[]): Promise { const [count, filepath, url] = parameters; + + if (!count) { + console.info('Missing parameter, how many offers do you need to generate ?'); + return; + } + + if (!filepath) { + console.info('Missing parameter, which file will we write to?'); + return; + } + + if (!url) { + console.info('Missing parameter, where do we get the helpers for data generation?'); + return; + } + const offerCount = Number.parseInt(count, 10); try { diff --git a/src/cli/commands/import.command.ts b/src/cli/commands/import.command.ts index 52217e6..f71638c 100644 --- a/src/cli/commands/import.command.ts +++ b/src/cli/commands/import.command.ts @@ -1,14 +1,16 @@ import { Command } from './command.interface.js'; import { TSVFileReader } from '../../shared/libs/file-reader/index.js'; import { Offer } from '../../shared/types/index.js'; -import { getErrorMessage, getMongoURI } from '../../shared/helpers/index.js'; import { DefaultUserService, UserModel, UserService } from '../../shared/modules/user/index.js'; +import { OfferService, OfferModel, DefaultOfferService } from '../../shared/modules/offer/index.js'; import { DatabaseClient, MongoDatabaseClient } from '../../shared/libs/database-client/index.js'; import { Logger, ConsoleLogger } from '../../shared/libs/logger/index.js'; -import { DEFAULT_DB_PORT } from './command.constant.js'; +import { getErrorMessage, getMongoURI } from '../../shared/helpers/index.js'; +import { DEFAULT_DB_PORT, DEFAULT_USER_PASSWORD } from './command.constant.js'; export class ImportCommand implements Command { private userService: UserService; + private offerService: OfferService; private databaseClient: DatabaseClient; private logger: Logger; private salt: string; @@ -18,23 +20,52 @@ export class ImportCommand implements Command { 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 async onImportedOffer(offer: Offer, resolve: () => void) { + await this.saveOffer(offer); resolve(); } + private async saveOffer(offer: Offer) { + const user = await this.userService.findOrCreate({ + ...offer.user, + password: DEFAULT_USER_PASSWORD, + }, this.salt); + + await this.offerService.create({ + title: offer.title, + description: offer.description, + postDate: offer.postDate, + city: offer.city, + preview: offer.preview, + photos: offer.photos, + isPremium: offer.isPremium, + isFavorite: offer.isFavorite, + rating: offer.rating, + type: offer.type, + roomsCount: offer.roomsCount, + guestsCount: offer.guestsCount, + price: offer.price, + amenities: offer.amenities, + userId: user.id, + latitude: offer.coordinates.latitude, + longitude: offer.coordinates.longitude, + }); + } + private onCompleteImport(count: number) { console.info(`${count} rows imported.`); this.databaseClient.disconnect(); } - public getName(): string { - return '--import'; - } - 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; diff --git a/src/main.rest.ts b/src/main.rest.ts index d7fb968..d07c65f 100644 --- a/src/main.rest.ts +++ b/src/main.rest.ts @@ -3,13 +3,15 @@ import { Container } from 'inversify'; import { RestApplication } from './rest/index.js'; import { Component } from './shared/types/index.js'; -import { createUserContainer } from './shared/modules/user/index.js'; import { createRestApplicationContainer } from './rest/index.js'; +import { createUserContainer } from './shared/modules/user/index.js'; +import { createOfferContainer } from './shared/modules/offer/index.js'; async function bootstrap() { const appContainer = Container.merge( createRestApplicationContainer(), - createUserContainer() + createUserContainer(), + createOfferContainer(), ); const app = appContainer.get(Component.RestApplication); diff --git a/src/shared/libs/file-reader/tsv-file-reader.ts b/src/shared/libs/file-reader/tsv-file-reader.ts index f555e8b..7d22688 100644 --- a/src/shared/libs/file-reader/tsv-file-reader.ts +++ b/src/shared/libs/file-reader/tsv-file-reader.ts @@ -62,7 +62,7 @@ export class TSVFileReader extends EventEmitter implements FileReader { guestsCount: Number.parseInt(guestsCount, 10), price: Number.parseInt(price, 10), amenities: amenities.split(';') as Amenities[], - author: this.parseUser(name, email, avatarPath, password, userType) as User, + user: this.parseUser(name, email, avatarPath, password, userType) as User, coordinates: this.parseCoordinates(latitude, longitude) as Coordinates, }; } diff --git a/src/shared/libs/logger/console.logger.ts b/src/shared/libs/logger/console.logger.ts index b2b9f4b..dda763b 100644 --- a/src/shared/libs/logger/console.logger.ts +++ b/src/shared/libs/logger/console.logger.ts @@ -3,15 +3,15 @@ import { getErrorMessage } from '../../helpers/index.js'; export class ConsoleLogger implements Logger { info(message: string, ...args: unknown[]): void { - console.info(message, args); + console.info(message, ...args); } warn(message: string, ...args: unknown[]): void { - console.warn(message, args); + console.warn(message, ...args); } error(message: string, error: Error, ...args: unknown[]): void { - console.error(message, args); + console.error(message, ...args); console.error(`Error message: ${getErrorMessage(error)}`); } diff --git a/src/shared/libs/offer-generator/tsv-offer-generator.ts b/src/shared/libs/offer-generator/tsv-offer-generator.ts index 633d6b9..e59f792 100644 --- a/src/shared/libs/offer-generator/tsv-offer-generator.ts +++ b/src/shared/libs/offer-generator/tsv-offer-generator.ts @@ -8,7 +8,7 @@ import { getRandomItems, } from '../../helpers/index.js'; -import { CoordinatesCities } from '../../types/index.js'; +import { City, CoordinatesCities } from '../../types/index.js'; const MIN_PRICE = 100; const MAX_PRICE = 100000; @@ -35,7 +35,7 @@ export class TSVOfferGenerator implements OfferGenerator { .subtract(generateRandomValue(FIRST_WEEK_DAY, LAST_WEEK_DAY), 'day') .toISOString(); - const city = getRandomItem(this.mockData.cities); + const city = getRandomItem(this.mockData.cities) as City; const preview = 'preview.jpg'; const photos = this.mockData.photos.join(';'); const isPremium = generateRandomValue(0, 1); 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..ca4f62a --- /dev/null +++ b/src/shared/modules/offer/default-offer.service.ts @@ -0,0 +1,26 @@ +import { inject, injectable } from 'inversify'; +import { Logger } from '../../libs/logger/index.js'; +import { Component } from '../../types/component.enum.js'; +import { types } from '@typegoose/typegoose'; +import { OfferEntity } from './offer.entity.js'; +import { OfferService } from './offer-service.interface.js'; +import { createOfferDto } from './dto/create-offer.dto.js'; + +@injectable() +export class DefaultOfferService implements OfferService { + constructor( + @inject(Component.Logger) private readonly logger: Logger, + @inject(Component.OfferModel) private readonly offerModel: types.ModelType + ) {} + + public async create(dto: createOfferDto): Promise> { + const result = await this.offerModel.create(dto); + this.logger.info(`New offer created: ${dto.title}`); + + return result; + } + + public async findById(offerId: string): Promise | null> { + return this.offerModel.findById(offerId).exec(); + } +} 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..3e01eaf --- /dev/null +++ b/src/shared/modules/offer/dto/create-offer.dto.ts @@ -0,0 +1,27 @@ +import { + Amenities, + City, + Coordinates, + Photos, + PropertyType, +} from '../../../types/index.js'; + +export class createOfferDto { + public title: string; + public description: string; + public postDate: Date; + public city: City; + public preview: string; + public photos: Photos; + public isPremium: boolean; + public isFavorite: boolean; + public rating: number; + public type: PropertyType; + public roomsCount: number; + public guestsCount: number; + public price: number; + public amenities: Amenities[]; + public userId: string; + public latitude: Coordinates['latitude']; + public longitude: Coordinates['longitude']; +} diff --git a/src/shared/modules/offer/index.ts b/src/shared/modules/offer/index.ts new file mode 100644 index 0000000..846ad89 --- /dev/null +++ b/src/shared/modules/offer/index.ts @@ -0,0 +1,5 @@ +export { OfferEntity, OfferModel } from './offer.entity.js'; +export { createOfferDto } from './dto/create-offer.dto.js'; +export { OfferService } from './offer-service.interface.js'; +export { createOfferContainer } from './offer.container.js'; +export { DefaultOfferService } from './default-offer.service.js'; diff --git a/src/shared/modules/offer/offer-service.interface.ts b/src/shared/modules/offer/offer-service.interface.ts new file mode 100644 index 0000000..affa6dc --- /dev/null +++ b/src/shared/modules/offer/offer-service.interface.ts @@ -0,0 +1,8 @@ +import { DocumentType } from '@typegoose/typegoose'; +import { createOfferDto } from './dto/create-offer.dto.js'; +import { OfferEntity } from './offer.entity.js'; + +export interface OfferService { + create(dto: createOfferDto): Promise>; + findById(offerId: string): Promise | null>; +} diff --git a/src/shared/modules/offer/offer.container.ts b/src/shared/modules/offer/offer.container.ts new file mode 100644 index 0000000..70a0df3 --- /dev/null +++ b/src/shared/modules/offer/offer.container.ts @@ -0,0 +1,15 @@ +import { Container } from 'inversify'; +import { Component } from '../../types/component.enum.js'; +import { DefaultOfferService } from './default-offer.service.js'; +import { OfferService } from './offer-service.interface.js'; +import { OfferEntity, OfferModel } from './offer.entity.js'; +import { types } from '@typegoose/typegoose'; + +export function createOfferContainer() { + const offerContainer = new Container; + + offerContainer.bind(Component.OfferService).to(DefaultOfferService); + offerContainer.bind>(Component.OfferModel).toConstantValue(OfferModel); + + return offerContainer; +} diff --git a/src/shared/modules/offer/offer.entity.ts b/src/shared/modules/offer/offer.entity.ts new file mode 100644 index 0000000..442bbe3 --- /dev/null +++ b/src/shared/modules/offer/offer.entity.ts @@ -0,0 +1,118 @@ +import { + Ref, + defaultClasses, + getModelForClass, + modelOptions, + prop, +} from '@typegoose/typegoose'; + +import { Amenities, City, Coordinates, Photos, PropertyType } from '../../types/index.js'; +import { UserEntity } from '../user/user.entity.js'; + +const MIN_RATING = 1; +const MAX_RATING = 5; + +const MIN_ROOMS_COUNT = 1; +const MAX_ROOMS_COUNT = 8; + +const MIN_GUESTS_COUNT = 1; +const MAX_GUESTS_COUNT = 10; + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export interface OfferEntity extends defaultClasses.Base {} + +@modelOptions({ + schemaOptions: { + collection: 'offers', + timestamps: true, + } +}) + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +export class OfferEntity extends defaultClasses.TimeStamps { + @prop({ + required: true, + trim: true + }) + public title!: string; + + @prop({ required: true }) + public description!: string; + + @prop({ required: true }) + public postDate!: Date; + + @prop({ required: true }) + public city!: City; + + @prop({ required: true }) + public preview!: string; + + @prop({ + required: true, + type: () => [String] + }) + public photos!: Photos; + + @prop({ default: false }) + public isPremium!: boolean; + + @prop({ default: false }) + public isFavorite!: boolean; + + @prop({ + required: true, + min: MIN_RATING, + max: MAX_RATING, + }) + public rating!: number; + + @prop({ required: true }) + public type!: PropertyType; + + @prop({ + required: true, + min: MIN_ROOMS_COUNT, + max: MAX_ROOMS_COUNT, + }) + public roomsCount!: number; + + @prop({ + required: true, + min: MIN_GUESTS_COUNT, + max: MAX_GUESTS_COUNT, + }) + public guestsCount!: number; + + @prop({ required: true }) + public price!: number; + + @prop({ + required: true, + type: () => [String] + }) + public amenities!: Amenities[]; + + @prop({ + ref: UserEntity, + required: true + }) + public userId!: Ref; + + @prop({ + required: true, + type: () => Number + }) + public latitude!: Coordinates['latitude']; + + @prop({ + required: true, + type: () => Number + }) + public longitude!: Coordinates['longitude']; + + @prop({ default: 0 }) + public commentCount!: number; +} + +export const OfferModel = getModelForClass(OfferEntity); diff --git a/src/shared/modules/user/dto/create-user.dto.ts b/src/shared/modules/user/dto/create-user.dto.ts index 60ad074..2aec763 100644 --- a/src/shared/modules/user/dto/create-user.dto.ts +++ b/src/shared/modules/user/dto/create-user.dto.ts @@ -3,7 +3,7 @@ import { UserType } from '../../../types/index.js'; export class CreateUserDto { public name: string; public email: string; - public avatarPath: string; + public avatarPath?: string; public password: string; public type: UserType; } diff --git a/src/shared/modules/user/user.entity.ts b/src/shared/modules/user/user.entity.ts index 7f66e82..e4e1357 100644 --- a/src/shared/modules/user/user.entity.ts +++ b/src/shared/modules/user/user.entity.ts @@ -13,20 +13,20 @@ export interface UserEntity extends defaultClasses.Base {} }) // eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export class UserEntity extends defaultClasses.TimeStamps implements User { - @prop({ required: true, default: '' }) - public name: string; + @prop({ default: '' }) + public name!: string; - @prop({ unique: true, required: true }) - public email: string; + @prop({ unique: true }) + public email!: string; - @prop({ required: false, default: '' }) - public avatarPath: string | undefined; + @prop({ default: '' }) + public avatarPath?: string; - @prop({ required: true, default: '' }) - public password: string; + @prop({ default: '' }) + public password!: string; - @prop({ required: true, enum: UserType, default: UserType.Regular }) - public type: UserType; + @prop({ enum: UserType, default: UserType.Regular }) + public type!: UserType; constructor(userData: User) { super(); diff --git a/src/shared/types/component.enum.ts b/src/shared/types/component.enum.ts index a65ab61..3c8e70e 100644 --- a/src/shared/types/component.enum.ts +++ b/src/shared/types/component.enum.ts @@ -5,4 +5,6 @@ export const Component = { DatabaseClient: Symbol.for('DatabaseClient'), UserService: Symbol.for('UserService'), UserModel: Symbol.for('UserModel'), + OfferService: Symbol.for('OfferService'), + OfferModel: Symbol.for('OfferModel'), } as const; diff --git a/src/shared/types/offer/coordinates-cities.const.ts b/src/shared/types/offer/coordinates-cities.const.ts index eb66d3a..7e87155 100644 --- a/src/shared/types/offer/coordinates-cities.const.ts +++ b/src/shared/types/offer/coordinates-cities.const.ts @@ -8,4 +8,4 @@ export const CoordinatesCities: Record = { [Cities.Amsterdam]: { latitude: 52.370216, longitude: 4.895168 }, [Cities.Hamburg]: { latitude: 53.550341, longitude: 10.000654 }, [Cities.Dusseldorf]: { latitude: 51.225402, longitude: 6.776314 }, -}; +} as const; diff --git a/src/shared/types/offer/offer.type.ts b/src/shared/types/offer/offer.type.ts index 1baa338..1c11234 100644 --- a/src/shared/types/offer/offer.type.ts +++ b/src/shared/types/offer/offer.type.ts @@ -20,6 +20,6 @@ export type Offer = { guestsCount: number, price: number, amenities: Amenities[], - author: User, + user: User, coordinates: Coordinates, } diff --git a/src/shared/types/user/user-type.enum.ts b/src/shared/types/user/user-type.enum.ts index 8530d2a..a0ece3b 100644 --- a/src/shared/types/user/user-type.enum.ts +++ b/src/shared/types/user/user-type.enum.ts @@ -1,4 +1,4 @@ export enum UserType { - Regular = 'Regular', - Pro = 'Pro', + Regular = 'regular', + Pro = 'pro', }