From 83e842232b2822b368a5803dc777bcc6e0987b2d Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 27 Oct 2023 12:58:38 +0400 Subject: [PATCH 01/14] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 720bded..576bb6d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # node-mentoring-shop -https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/express-layered-architecture/task +[https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/nosql/homework](https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/nosql/homework) We are going to create an Express application for online shop which sells different types of products (like e.g Amazon). -In this task we are going to implement functionality for managing carts, creating orders and products. +In this task we will modify the application we created in [Express and Layered Architecture](https://github.com/kandalova/node-mentoring-shop/pull/1) module by moving data storage to NoSQL database. The application has 4 primary entities: @@ -27,12 +27,12 @@ One User can have multiple Order. Each Orderxxx.repository.ts contains functions to retrieve data (data access layer), xxx.service.ts contains services that implement business logic, xxx.controller.ts contains functions that manage status codes/responses returned (presentation layer). -- Data is stored either in memory or on file system. -- joi is used to validate data in PUT /api/profile/cart. -- Simple authentication middleware is added to check if user with such id exists. User id is passed in x-user-id header. -- Order entity has copy of products. If you have only product id in order, it may lead to inconsistency. For example, if user creates an order and after that price is changed, the order price shouldn't be changed. -- For ```DELETE``` ```/api/profile/cart``` soft-delete approach is be used. Make sure that client of your API will not know that soft-delete approach is used. +- Data is stored in `MongoDB` database. +- `Docker` image is used for local development (check Running [MongoDB as a Docker Container](https://www.baeldung.com/linux/mongodb-as-docker-container#2-building-container-using-a-compose-file) for an example of docker-compose file). +- `Mongoose` is used as ODM for querying. +- `Data Access Layer` is rewritten to query `MongoDB`. +- Models are created based on entity schemas used in Express and Layered Architecture module. +- Models have proper relations between each other based on information specified above. **API endpoints** From 99c2a3fb325ae165f2ca2b41354d63c71bee447c Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Tue, 31 Oct 2023 15:49:30 +0600 Subject: [PATCH 02/14] Add docker file --- .gitignore | 2 ++ docker-compose.yaml | 14 ++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 docker-compose.yaml diff --git a/.gitignore b/.gitignore index c6bba59..9c508c3 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,5 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +/venv-podman diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..c33fb20 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,14 @@ +version: '3' +services: + mongo: + ports: + - '27017:27017' + container_name: mongo + restart: always + logging: + options: + max-size: 1g + environment: + - MONGO_INITDB_ROOT_USERNAME=mongoadmin + - MONGO_INITDB_ROOT_PASSWORD=123admin + image: mongo From dcd204b47f96cb8fb350c563538c7da406de903a Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Tue, 31 Oct 2023 15:51:20 +0600 Subject: [PATCH 03/14] Install mongo package --- package-lock.json | 147 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 3 +- 2 files changed, 143 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9de0178..d64d61b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,12 +12,13 @@ "express": "^4.18.2", "joi": "^17.11.0", "lodash.clonedeep": "^4.5.0", + "mongodb": "^6.2.0", "uuid": "^9.0.1" }, "devDependencies": { "@types/express": "^4.17.18", "@types/lodash.clonedeep": "^4.5.7", - "@types/node": "^20.8.2", + "@types/node": "^20.8.10", "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", @@ -221,6 +222,14 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.1.tgz", + "integrity": "sha512-t7c5K033joZZMspnHg/gWPE4kandgc2OxE74aYOtGKfgB9VPuVJPix0H6fhmm2erj5PBJ21mqcx34lpIGtUCsQ==", + "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", @@ -375,10 +384,12 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.8.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz", - "integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w==", - "dev": true + "version": "20.8.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", + "integrity": "sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/qs": { "version": "6.9.8", @@ -425,6 +436,20 @@ "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==", "dev": true }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.2.tgz", + "integrity": "sha512-uNv6b/uGRLlCVmelat2rA8bcVd3k/42mV2EmjhPh6JLkd35T5bgwR/t6xy7a9MWhd9sixIeBUzhBenvk3NO+DQ==" + }, + "node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "6.7.4", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz", @@ -906,6 +931,14 @@ "node": ">=8" } }, + "node_modules/bson": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.2.0.tgz", + "integrity": "sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -1970,6 +2003,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", @@ -2047,6 +2085,60 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.2.0.tgz", + "integrity": "sha512-d7OSuGjGWDZ5usZPqfvb36laQ9CPhnWkAGHT61x5P95p/8nMVeH8asloMwW6GcYFeB0Vj4CB/1wOTDG2RA9BFA==", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.0", + "bson": "^6.2.0", + "mongodb-connection-string-url": "^2.6.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": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2297,7 +2389,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" } @@ -2576,6 +2667,14 @@ "node": ">=8" } }, + "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/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2658,6 +2757,17 @@ "nodetouch": "bin/nodetouch.js" } }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -2768,6 +2878,11 @@ "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==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2819,6 +2934,26 @@ "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": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/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 59441cb..43add61 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "devDependencies": { "@types/express": "^4.17.18", "@types/lodash.clonedeep": "^4.5.7", - "@types/node": "^20.8.2", + "@types/node": "^20.8.10", "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", @@ -27,6 +27,7 @@ "express": "^4.18.2", "joi": "^17.11.0", "lodash.clonedeep": "^4.5.0", + "mongodb": "^6.2.0", "uuid": "^9.0.1" } } From 8207b5b8d21bad56b4c42102c4f737834ac4ea48 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Tue, 31 Oct 2023 15:55:30 +0600 Subject: [PATCH 04/14] Add mongoos package --- package-lock.json | 161 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 + 2 files changed, 163 insertions(+) diff --git a/package-lock.json b/package-lock.json index d64d61b..abf2a2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,11 +13,13 @@ "joi": "^17.11.0", "lodash.clonedeep": "^4.5.0", "mongodb": "^6.2.0", + "mongoose": "^7.6.4", "uuid": "^9.0.1" }, "devDependencies": { "@types/express": "^4.17.18", "@types/lodash.clonedeep": "^4.5.7", + "@types/mongoose": "^5.11.97", "@types/node": "^20.8.10", "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.7.4", @@ -383,6 +385,16 @@ "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==", "dev": true }, + "node_modules/@types/mongoose": { + "version": "5.11.97", + "resolved": "https://registry.npmjs.org/@types/mongoose/-/mongoose-5.11.97.tgz", + "integrity": "sha512-cqwOVYT3qXyLiGw7ueU2kX9noE8DPGRY6z8eUxudhXY8NZ7DMKYAxyZkLSevGfhCX3dO/AoX5/SO9lAzfjon0Q==", + "deprecated": "Mongoose publishes its own types, so you do not need to install this package.", + "dev": true, + "dependencies": { + "mongoose": "*" + } + }, "node_modules/@types/node": { "version": "20.8.10", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.10.tgz", @@ -1822,6 +1834,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -1929,6 +1946,14 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/kareem": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", + "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", @@ -2139,6 +2164,115 @@ "whatwg-url": "^11.0.0" } }, + "node_modules/mongoose": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.6.4.tgz", + "integrity": "sha512-kadPkS/f5iZJrrMxxOvSoOAErXmdnb28lMvHmuYgmV1ZQTpRqpp132PIPHkJMbG4OC2H0eSXYw/fNzYTH+LUcw==", + "dependencies": { + "bson": "^5.5.0", + "kareem": "2.5.1", + "mongodb": "5.9.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "16.0.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongoose/node_modules/mongodb": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.0.tgz", + "integrity": "sha512-g+GCMHN1CoRUA+wb1Agv0TI4YTSiWr42B5ulkiAfLLHitGK1R+PkSAf3Lr5rPZwi/3F04LiaZEW0Kxro9Fi2TA==", + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "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/mquery/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/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==" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2646,6 +2780,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", + "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -2667,6 +2806,28 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", diff --git a/package.json b/package.json index 43add61..c1c88b9 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "devDependencies": { "@types/express": "^4.17.18", "@types/lodash.clonedeep": "^4.5.7", + "@types/mongoose": "^5.11.97", "@types/node": "^20.8.10", "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.7.4", @@ -28,6 +29,7 @@ "joi": "^17.11.0", "lodash.clonedeep": "^4.5.0", "mongodb": "^6.2.0", + "mongoose": "^7.6.4", "uuid": "^9.0.1" } } From c750c2851a32296db30ff6095d0b2fdf224a6843 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Wed, 1 Nov 2023 18:34:59 +0600 Subject: [PATCH 05/14] Update README --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index 576bb6d..baf858a 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,20 @@ One User can have multiple Order. Each Order + +**Setup for startring (once)** +In the project root folder + +- Install [podman](https://podman.io/docs/installation) +- Install [podman-compose](https://github.com/containers/podman-compose#installation) +- Init podman machine `podman machine init` +- Install [MongoDB server](https://www.mongodb.com/try/download/community) +- Install MongoDB tools to connect to MongoDB: [Shell](https://www.mongodb.com/try/download/shell) or [Compass Gui](https://www.mongodb.com/try/download/compass) + +**To start(every time)** +- `podman machine start` - start virtual machine +- `podman-compose up -d` - starts container from `docker-compose.yaml` +- `podman ps` - check running containers +- Connect to Mongo via Compass or shell `mongosh --port 27017` + +[Running MongoDB as a Docker Container](https://www.baeldung.com/linux/mongodb-as-docker-container#2-building-container-using-a-compose-file) From 56b1929334202037ca18476f127ed284d63e14ec Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Wed, 1 Nov 2023 21:04:38 +0600 Subject: [PATCH 06/14] Add user and product schemas --- README.md | 3 +++ src/index.ts | 32 ++++++++++++++++++++++++++++++-- src/scheme/ProductScheme.ts | 9 +++++++++ src/scheme/UserScheme.ts | 10 +++++++++- src/seeder/productSeeder.ts | 9 +++++++++ src/seeder/userSeeder.ts | 9 +++++++++ 6 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 src/seeder/productSeeder.ts create mode 100644 src/seeder/userSeeder.ts diff --git a/README.md b/README.md index baf858a..aa0524a 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,6 @@ In the project root folder - Connect to Mongo via Compass or shell `mongosh --port 27017` [Running MongoDB as a Docker Container](https://www.baeldung.com/linux/mongodb-as-docker-container#2-building-container-using-a-compose-file) + +**First time starting** +- Create your first database in MongoDB. Then, create your first collection (Example - User). diff --git a/src/index.ts b/src/index.ts index 4071da3..4a58f24 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,11 @@ import cartRouter from "./controller/cart.controller.ts"; import productRouter from "./controller/product.controller.ts"; import { ResponseError, getForbidenError, getUnauthorizedError } from "./utils/errors.ts"; import { getResponseError } from "./utils/utils.ts"; +import { connect, disconnect } from "mongoose"; +// import { UserModel } from "./scheme/UserScheme.ts"; +// import { runUserSeeder } from "./seeder/userSeeder.ts"; +// import { ProductModel } from "./scheme/ProductScheme.ts"; +// import { runProductSeeder } from "./seeder/productSeeder.ts"; export const app = express(); const PORT = 3000; @@ -33,7 +38,30 @@ const errorHandler = async (err: Error | ResponseError, _req: Request, res: Resp app.use('/api/profile/cart', headerHandler, cartRouter, errorHandler); app.use('/api/products', headerHandler, productRouter, errorHandler); +export const init = (async () => { + try { + await connect("mongodb://127.0.0.1:27017/NM_SHOP"); + app.listen(PORT, () => { + console.log(`Server is Fire at http://localhost:${PORT}`); + }); + //mock data for first run + // const user = await UserModel.findOne(); + // if (!user) { + // await runUserSeeder(); + // } + // const product = await ProductModel.findOne(); + // if (!product) { + // await runProductSeeder(); + // } + } + catch (err) { + console.log(err); + } +})(); -app.listen(PORT, () => { - console.log(`Server is Fire at http://localhost:${PORT}`); +// listen (ctrl-c) +process.on("SIGINT", async () => { + await disconnect(); + console.log("Server is disconnected"); + process.exit(); }); diff --git a/src/scheme/ProductScheme.ts b/src/scheme/ProductScheme.ts index d834db1..1caeffc 100644 --- a/src/scheme/ProductScheme.ts +++ b/src/scheme/ProductScheme.ts @@ -1,3 +1,4 @@ +import { Schema, model } from "mongoose"; import { IResponse } from "./ServiceUtils"; export interface IProduct { @@ -14,3 +15,11 @@ export interface IProductResponse extends IResponse { export interface IProductsResponse extends IResponse { data: IProduct[], } + +const productSchema = new Schema({ + title: { type: String, required: true }, + description: { type: String, required: true }, + price: { type: Number, required: true }, +}); + +export const ProductModel = model("Product", productSchema); diff --git a/src/scheme/UserScheme.ts b/src/scheme/UserScheme.ts index 0fe48b5..893dc2a 100644 --- a/src/scheme/UserScheme.ts +++ b/src/scheme/UserScheme.ts @@ -1,3 +1,11 @@ +import { Schema, model } from "mongoose"; + export interface IUser { - id: string, + name: string, } + +const userSchema = new Schema({ + name: { type: String, required: true }, +}) + +export const UserModel = model("User", userSchema); diff --git a/src/seeder/productSeeder.ts b/src/seeder/productSeeder.ts new file mode 100644 index 0000000..6f4d101 --- /dev/null +++ b/src/seeder/productSeeder.ts @@ -0,0 +1,9 @@ +import { productDB } from "../db/db"; +import { ProductModel } from "../scheme/ProductScheme"; + +export const runProductSeeder = async () => { + productDB.forEach(async ({ title, description, price }) => { + const newProduct = new ProductModel({ title, description, price }); + await newProduct.save(); + }); +} diff --git a/src/seeder/userSeeder.ts b/src/seeder/userSeeder.ts new file mode 100644 index 0000000..a8c3548 --- /dev/null +++ b/src/seeder/userSeeder.ts @@ -0,0 +1,9 @@ +import { userDB } from "../db/db" +import { UserModel } from "../scheme/UserScheme"; + +export const runUserSeeder = async () => { + userDB.forEach(async (value, i) => { + const newUser = new UserModel({ name: `User-${i}` }); + await newUser.save(); + }); +} From f9e23b201b085ecd6dc654edc0b058e3ec00da44 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Wed, 1 Nov 2023 21:15:08 +0600 Subject: [PATCH 07/14] Move products db to model --- src/service/product.service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/service/product.service.ts b/src/service/product.service.ts index 3b7708a..9f1d42d 100644 --- a/src/service/product.service.ts +++ b/src/service/product.service.ts @@ -1,10 +1,9 @@ -import { findProduct, findProducts } from "../repository/product.repository"; -import { IProductResponse, IProductsResponse } from "../scheme/ProductScheme"; +import { IProductResponse, IProductsResponse, ProductModel } from "../scheme/ProductScheme"; import { throwNoProductExists } from "../utils/errors"; import { getProductResponse, getProductsResponse } from "../utils/productUtils"; export const getProduct = async (productId: string): Promise => { - const product = await findProduct(productId); + const product = await ProductModel.findById(productId); if (!product) { throwNoProductExists(); } @@ -14,6 +13,6 @@ export const getProduct = async (productId: string): Promise => { - const products = await findProducts(); + const products = await ProductModel.find(); return getProductsResponse(products); }; From d77431bda4d9c00e8865d4e259712d6fb945ffb9 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Wed, 1 Nov 2023 21:20:39 +0600 Subject: [PATCH 08/14] Move user db to model --- src/index.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/index.ts b/src/index.ts index 4a58f24..5e032b0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ import express, { NextFunction, Request, Response } from "express"; -import { findUser } from "./repository/user.repository.ts"; import cartRouter from "./controller/cart.controller.ts"; import productRouter from "./controller/product.controller.ts"; import { ResponseError, getForbidenError, getUnauthorizedError } from "./utils/errors.ts"; import { getResponseError } from "./utils/utils.ts"; import { connect, disconnect } from "mongoose"; +import { UserModel } from "./scheme/UserScheme.ts"; // import { UserModel } from "./scheme/UserScheme.ts"; // import { runUserSeeder } from "./seeder/userSeeder.ts"; // import { ProductModel } from "./scheme/ProductScheme.ts"; @@ -14,15 +14,20 @@ export const app = express(); const PORT = 3000; const headerHandler = async (req: Request, _: Response, next: NextFunction) => { - const userId = req.header('x-user-id') ?? ''; - if (!userId) { - next(getForbidenError()); + try { + const userId = req.header('x-user-id') ?? ''; + if (!userId) { + next(getForbidenError()); + } + const user = await UserModel.findById(userId); + if (!user) { + next(getUnauthorizedError()); + } + next(); } - const user = await findUser(userId); - if (!user) { - next(getUnauthorizedError()); + catch (err) { + next(err); } - next(); } const errorHandler = async (err: Error | ResponseError, _req: Request, res: Response, _next: NextFunction) => { @@ -63,5 +68,5 @@ export const init = (async () => { process.on("SIGINT", async () => { await disconnect(); console.log("Server is disconnected"); - process.exit(); + process.exit(); }); From c7642f50627ea5af2b50c77d5e11e1eb7b3dc574 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Wed, 1 Nov 2023 21:23:14 +0600 Subject: [PATCH 09/14] Small refactor --- src/controller/cart.controller.ts | 1 - src/index.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controller/cart.controller.ts b/src/controller/cart.controller.ts index 13ab135..87d23ac 100644 --- a/src/controller/cart.controller.ts +++ b/src/controller/cart.controller.ts @@ -2,7 +2,6 @@ import express, { NextFunction, Request, Response } from "express"; import { createOrGetCart, createOrder, deleteCart, updateCart } from "../service/cart.service"; import Joi from "joi"; import { getPutSchemeError } from "../utils/errors"; -// import { promiseHandler } from "../utils/utils"; const cartRouter = express.Router(); cartRouter.use(express.json()); diff --git a/src/index.ts b/src/index.ts index 5e032b0..e30a017 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,8 @@ import { ResponseError, getForbidenError, getUnauthorizedError } from "./utils/e import { getResponseError } from "./utils/utils.ts"; import { connect, disconnect } from "mongoose"; import { UserModel } from "./scheme/UserScheme.ts"; -// import { UserModel } from "./scheme/UserScheme.ts"; + +//for seeders // import { runUserSeeder } from "./seeder/userSeeder.ts"; // import { ProductModel } from "./scheme/ProductScheme.ts"; // import { runProductSeeder } from "./seeder/productSeeder.ts"; From 778fbae7f6ba93ffbd2e16dff7bce6126bccbfbd Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Thu, 2 Nov 2023 21:45:32 +0600 Subject: [PATCH 10/14] Add and use cart model --- src/controller/cart.controller.ts | 12 +---- src/index.ts | 6 +++ src/scheme/CartScheme.ts | 27 ++++++++++- src/scheme/ProductScheme.ts | 2 +- src/scheme/UserScheme.ts | 2 +- src/service/cart.service.ts | 81 ++++++++++++++++--------------- 6 files changed, 78 insertions(+), 52 deletions(-) diff --git a/src/controller/cart.controller.ts b/src/controller/cart.controller.ts index 87d23ac..a6bd7b7 100644 --- a/src/controller/cart.controller.ts +++ b/src/controller/cart.controller.ts @@ -7,7 +7,7 @@ const cartRouter = express.Router(); cartRouter.use(express.json()); const putSchema = Joi.object({ - productId: Joi.string().uuid().required(), + productId: Joi.string().required(), count: Joi.number().integer().min(0).max(20).required() }) @@ -19,25 +19,17 @@ const putValidator = async (req: Request, _: Response, next: NextFunction) => { catch (err) { next(getPutSchemeError()); } - } -// cartRouter.post("/", async (req:Request, res:Response, next:NextFunction) => { -// const userId = req.header('x-user-id')!; -// const cart = await promiseHandler(createCart(userId), next); -// res.status(201).send(cart); -// }); - cartRouter.get("/", async (req: Request, res: Response, next: NextFunction) => { try { const userId = req.header('x-user-id')!; - const cart = await createOrGetCart(userId, true); + const cart = await createOrGetCart(userId); res.send(cart); } catch (err) { next(err); } - }); cartRouter.delete("/", async (req: Request, res: Response, next: NextFunction) => { diff --git a/src/index.ts b/src/index.ts index e30a017..06157ba 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,8 @@ import { UserModel } from "./scheme/UserScheme.ts"; // import { runUserSeeder } from "./seeder/userSeeder.ts"; // import { ProductModel } from "./scheme/ProductScheme.ts"; // import { runProductSeeder } from "./seeder/productSeeder.ts"; +//for cart check +// import { CartModel } from "./scheme/CartScheme.ts"; export const app = express(); const PORT = 3000; @@ -59,6 +61,10 @@ export const init = (async () => { // if (!product) { // await runProductSeeder(); // } + //check cart + // const doc = new CartModel({items: [{ count: 5 }]}); + // console.log('doc', doc); + // console.log(doc.items[0].ownerDocument()===doc); } catch (err) { console.log(err); diff --git a/src/scheme/CartScheme.ts b/src/scheme/CartScheme.ts index 77035bf..120bb7a 100644 --- a/src/scheme/CartScheme.ts +++ b/src/scheme/CartScheme.ts @@ -1,5 +1,6 @@ -import { IProduct } from "./ProductScheme"; +import { Schema, Types, model } from "mongoose"; import { IResponse } from "./ServiceUtils"; +import { IProduct } from "./ProductScheme"; export interface ICart { id: string; // uuid @@ -13,6 +14,30 @@ export interface ICartItem { count: number, } +export interface ICartItemSchema { + product: Types.ObjectId, + count: number, +} + +export interface ICartSchema { + user: Types.ObjectId; + isDeleted: boolean; + items: Types.DocumentArray; +} + +const cartItemSchema = new Schema({ + product: { type: Schema.Types.ObjectId, ref: 'Product' }, + count: { type: Number, required: true }, +}, { versionKey: false }); + +const cartSchema = new Schema({ + user: { type: Schema.Types.ObjectId, ref: 'User' }, + isDeleted: { type: Boolean, default: false }, + items: [cartItemSchema], +}, { versionKey: false }); + +export const CartModel = model('Cart', cartSchema); + export interface ICartItemByID { productId: string, count: number, diff --git a/src/scheme/ProductScheme.ts b/src/scheme/ProductScheme.ts index 1caeffc..124eb5e 100644 --- a/src/scheme/ProductScheme.ts +++ b/src/scheme/ProductScheme.ts @@ -20,6 +20,6 @@ const productSchema = new Schema({ title: { type: String, required: true }, description: { type: String, required: true }, price: { type: Number, required: true }, -}); +}, { versionKey: false }); export const ProductModel = model("Product", productSchema); diff --git a/src/scheme/UserScheme.ts b/src/scheme/UserScheme.ts index 893dc2a..d3b8c38 100644 --- a/src/scheme/UserScheme.ts +++ b/src/scheme/UserScheme.ts @@ -6,6 +6,6 @@ export interface IUser { const userSchema = new Schema({ name: { type: String, required: true }, -}) +}, { versionKey: false }) export const UserModel = model("User", userSchema); diff --git a/src/service/cart.service.ts b/src/service/cart.service.ts index 0beec0e..e6cd261 100644 --- a/src/service/cart.service.ts +++ b/src/service/cart.service.ts @@ -1,66 +1,69 @@ -import { deleteCartItem, findUserCart, findUserCartIdx, getCartByIdx, getCartProductIdx, pushCart, pushCartItem, updateCartItemCount, updateCartProperty } from "../repository/cart.repository"; +import { deleteCartItem, findUserCartIdx, getCartByIdx, getCartProductIdx, pushCartItem, updateCartItemCount, updateCartProperty } from "../repository/cart.repository"; import { pushOrder } from "../repository/order.repository"; import { findProduct } from "../repository/product.repository"; -import { CartEditableProperties, ICartItemByID, ICartResponse, IDeleteCartResponse } from "../scheme/CartScheme"; +import { CartEditableProperties, CartModel, ICartItemByID, ICartResponse, ICartSchema, IDeleteCartResponse } from "../scheme/CartScheme"; import { IOrderInfo } from "../scheme/OrderScheme"; -import { generateCart, generateOrder, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; -import { throwCartExistsError, throwEmptyCart, throwNoCartExists, throwNoProductExists } from "../utils/errors"; +import { ProductModel } from "../scheme/ProductScheme"; +import { generateOrder, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; +import { throwEmptyCart, throwNoCartExists, throwNoProductExists } from "../utils/errors"; - - -export const createOrGetCart = async (userId: string, isGetOrCreate = false): Promise => { - const userCart = await findUserCart(userId); - if (userCart && isGetOrCreate) { - return getCartResponse(userCart); +export const createOrGetCart = async (userId: string): Promise => { + const userCart = await CartModel.findOne({ user: userId, isDeleted: false }); + if (userCart) { + return userCart; + // return getCartResponse(userCart); } - if (userCart && !isGetOrCreate) { - throwCartExistsError(userId); - } - const newCart = generateCart(userId); - await pushCart(newCart); - return getCartResponse(newCart); + const newCart = await CartModel.create({ user: userId }); + return newCart; + // return getCartResponse(newCart); }; export const deleteCart = async (userId: string): Promise => { - const cartIdx = await findUserCartIdx(userId); - if (cartIdx === -1) { + const userCart = await CartModel.findOne({ user: userId, isDeleted: false }); + if (!userCart) { throwNoCartExists(userId); } - await updateCartProperty(cartIdx, CartEditableProperties.IsDeleted, true); + else { + userCart.isDeleted = true; + await userCart.save(); + } const response = getDeleteCartResponse(); return response; } -export const postCart = async (userId: string): Promise => { +export const postCart = async (userId: string): Promise => { return createOrGetCart(userId); }; -export const getCart = async (userId: string): Promise => { - return createOrGetCart(userId, true); +export const getCart = async (userId: string): Promise => { + return createOrGetCart(userId); } -export const updateCart = async (userId: string, { productId, count }: ICartItemByID): Promise => { - const cartIdx = await findUserCartIdx(userId); - if (cartIdx === -1) { +export const updateCart = async (userId: string, { productId, count }: ICartItemByID): Promise => { + const userCart = await CartModel.findOne({ user: userId, isDeleted: false }); + const product = await ProductModel.findById(productId); + + if (!product) { + throwNoProductExists(); + } + if (!userCart) { throwEmptyCart(userId); } - const productIdx = await getCartProductIdx(cartIdx, productId); - if (count > 0 && productIdx === -1) { - const product = await findProduct(productId); - if (!product) { - throwNoProductExists(); + else { + const indexToUpdate = userCart.items.findIndex(item => item.product.toString() === productId); + if (indexToUpdate === -1 && count > 0) { + userCart.items.push({ product: productId, count: count }); } - else { - await pushCartItem(cartIdx, { product, count }) + if (indexToUpdate >= 0 && count > 0) { + userCart.items[indexToUpdate].count = count; } + if (indexToUpdate >= 0 && count === 0) { + userCart.items.splice(indexToUpdate, 1); + } + const updatedCart = (await userCart.save()).populate('items.product'); + return updatedCart; } - if (count > 0 && productIdx >= 0) { - await updateCartItemCount(cartIdx, productIdx, count); - } - if (count === 0 && productIdx >= 0) { - await deleteCartItem(cartIdx, productIdx); - } - return await getCartResponse(await getCartByIdx(cartIdx)); + // return await getCartResponse(await getCartByIdx(cartIdx)); } export const createOrder = async (userId: string, orderInfo: IOrderInfo) => { From 2a7e5a43996ffc1a6d379135f3d18bb432a1b7df Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 3 Nov 2023 00:35:28 +0600 Subject: [PATCH 11/14] Add order schema and logic --- src/scheme/CartScheme.ts | 14 +++-- src/scheme/OrderScheme.ts | 50 +++++++++++++++++- src/scheme/ProductScheme.ts | 6 ++- src/service/cart.service.ts | 102 ++++++++++++++++-------------------- src/utils/cartUtils.ts | 32 ++++++----- 5 files changed, 125 insertions(+), 79 deletions(-) diff --git a/src/scheme/CartScheme.ts b/src/scheme/CartScheme.ts index 120bb7a..14b6769 100644 --- a/src/scheme/CartScheme.ts +++ b/src/scheme/CartScheme.ts @@ -1,6 +1,7 @@ import { Schema, Types, model } from "mongoose"; import { IResponse } from "./ServiceUtils"; import { IProduct } from "./ProductScheme"; +import { Document } from "mongoose"; export interface ICart { id: string; // uuid @@ -20,30 +21,35 @@ export interface ICartItemSchema { } export interface ICartSchema { + _id: Types.ObjectId; user: Types.ObjectId; isDeleted: boolean; items: Types.DocumentArray; } const cartItemSchema = new Schema({ - product: { type: Schema.Types.ObjectId, ref: 'Product' }, + product: { type: Schema.Types.ObjectId, ref: 'Product', required: true }, count: { type: Number, required: true }, }, { versionKey: false }); const cartSchema = new Schema({ - user: { type: Schema.Types.ObjectId, ref: 'User' }, - isDeleted: { type: Boolean, default: false }, + user: { type: Schema.Types.ObjectId, ref: 'User', required: true, }, + isDeleted: { type: Boolean, default: false, required: true }, items: [cartItemSchema], }, { versionKey: false }); export const CartModel = model('Cart', cartSchema); +export type PopulatedCart = Omit & { + items: ICartItem[]; +}; + export interface ICartItemByID { productId: string, count: number, } -export type OmitCart = Omit; +export type OmitCart = Pick; export enum CartEditableProperties { Items = 'items', diff --git a/src/scheme/OrderScheme.ts b/src/scheme/OrderScheme.ts index e07b3bc..09294bb 100644 --- a/src/scheme/OrderScheme.ts +++ b/src/scheme/OrderScheme.ts @@ -1,3 +1,4 @@ +import { Schema, Types, model } from "mongoose"; import { ICartItem } from "./CartScheme"; type ORDER_STATUS = 'created' | 'completed'; @@ -21,10 +22,55 @@ export interface IOrder { total: number; } - -export type IOrderInfo = Pick; +export interface IOrderSchema { + user: Types.ObjectId; + cart: Types.ObjectId; + items: ICartItem[]; + payment: { + type: string, + address?: unknown, + creditCard?: unknown, + }, + delivery: { + type: string, + address: unknown, + }, + comments: string, + status: OrderStatuses; + total: number; +} export enum OrderStatuses { Created = 'created', Complited = "completed" } + +const orderSchema = new Schema({ + user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, + cart: { type: Schema.Types.ObjectId, ref: 'Cart', required: true }, + items: { + type: [{ + title: { type: String, required: true }, + description: { type: String, required: true }, + price: { type: Number, required: true }, + product: { type: String, required: true } + }], + }, + payment: { + type: { type: String, required: true }, + address: Schema.Types.Mixed, + creditCard: Schema.Types.Mixed, + }, + delivery: { + type: { type: String, required: true }, + address: { type: Schema.Types.Mixed, required: true }, + }, + comments: { type: String }, + status: { type: String, enum: Object.values(OrderStatuses), required: true, default: OrderStatuses.Created }, + total: { type: Number, required: true }, +}, { versionKey: false }); + +export const OrderModel = model('Order', orderSchema); + + +export type IOrderInfo = Pick; diff --git a/src/scheme/ProductScheme.ts b/src/scheme/ProductScheme.ts index 124eb5e..1ccc44d 100644 --- a/src/scheme/ProductScheme.ts +++ b/src/scheme/ProductScheme.ts @@ -1,11 +1,12 @@ -import { Schema, model } from "mongoose"; +import { Schema, Types, model } from "mongoose"; import { IResponse } from "./ServiceUtils"; export interface IProduct { - id: string; + _id: Types.ObjectId; title: string; description: string; price: number; + product?: string; } export interface IProductResponse extends IResponse { @@ -20,6 +21,7 @@ const productSchema = new Schema({ title: { type: String, required: true }, description: { type: String, required: true }, price: { type: Number, required: true }, + product: String, }, { versionKey: false }); export const ProductModel = model("Product", productSchema); diff --git a/src/service/cart.service.ts b/src/service/cart.service.ts index e6cd261..36c03c7 100644 --- a/src/service/cart.service.ts +++ b/src/service/cart.service.ts @@ -1,82 +1,68 @@ -import { deleteCartItem, findUserCartIdx, getCartByIdx, getCartProductIdx, pushCartItem, updateCartItemCount, updateCartProperty } from "../repository/cart.repository"; -import { pushOrder } from "../repository/order.repository"; -import { findProduct } from "../repository/product.repository"; -import { CartEditableProperties, CartModel, ICartItemByID, ICartResponse, ICartSchema, IDeleteCartResponse } from "../scheme/CartScheme"; -import { IOrderInfo } from "../scheme/OrderScheme"; +import { CartModel, ICartItem, ICartItemByID, ICartResponse, IDeleteCartResponse } from "../scheme/CartScheme"; +import { IOrderInfo, OrderModel } from "../scheme/OrderScheme"; import { ProductModel } from "../scheme/ProductScheme"; -import { generateOrder, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; +import { generateOrderDTO, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; import { throwEmptyCart, throwNoCartExists, throwNoProductExists } from "../utils/errors"; -export const createOrGetCart = async (userId: string): Promise => { - const userCart = await CartModel.findOne({ user: userId, isDeleted: false }); +const findActiveUserCart = async (userId: string) => { + return await CartModel.findOne({ user: userId, isDeleted: false }).populate<{ items: ICartItem[] }>('items.product'); +} +const findActiveUserCartOrFail = async (userId: string) => { + return await CartModel.findOne({ user: userId, isDeleted: false }).orFail(() => throwNoCartExists(userId)); +} + +const findUserCartWithProductOrFail = async (userId: string) => { + return await CartModel + .findOne({ user: userId, isDeleted: false }).populate<{ items: ICartItem[] }>('items.product') + .orFail(() => throwNoCartExists(userId)); +} + +export const createOrGetCart = async (userId: string): Promise => { + const userCart = await findActiveUserCart(userId); if (userCart) { - return userCart; - // return getCartResponse(userCart); + return getCartResponse(userCart); } const newCart = await CartModel.create({ user: userId }); - return newCart; - // return getCartResponse(newCart); + return getCartResponse(newCart); }; export const deleteCart = async (userId: string): Promise => { - const userCart = await CartModel.findOne({ user: userId, isDeleted: false }); - if (!userCart) { - throwNoCartExists(userId); - } - else { - userCart.isDeleted = true; - await userCart.save(); - } + const userCart = await findActiveUserCartOrFail(userId); + userCart.isDeleted = true; + await userCart.save(); const response = getDeleteCartResponse(); return response; } -export const postCart = async (userId: string): Promise => { - return createOrGetCart(userId); -}; - -export const getCart = async (userId: string): Promise => { - return createOrGetCart(userId); -} - -export const updateCart = async (userId: string, { productId, count }: ICartItemByID): Promise => { - const userCart = await CartModel.findOne({ user: userId, isDeleted: false }); - const product = await ProductModel.findById(productId); +export const updateCart = async (userId: string, { productId, count }: ICartItemByID): Promise => { + const userCart = await findActiveUserCartOrFail(userId); + const indexToUpdate = userCart.items.findIndex(item => item.product.toString() === productId); - if (!product) { - throwNoProductExists(); + if (indexToUpdate === -1 && count > 0) { + const product = await ProductModel.findById(productId); + if (!product) { + throwNoProductExists(); + } + userCart.items.push({ product: productId, count: count }); } - if (!userCart) { - throwEmptyCart(userId); + if (indexToUpdate >= 0 && count > 0) { + userCart.items[indexToUpdate].count = count; } - else { - const indexToUpdate = userCart.items.findIndex(item => item.product.toString() === productId); - if (indexToUpdate === -1 && count > 0) { - userCart.items.push({ product: productId, count: count }); - } - if (indexToUpdate >= 0 && count > 0) { - userCart.items[indexToUpdate].count = count; - } - if (indexToUpdate >= 0 && count === 0) { - userCart.items.splice(indexToUpdate, 1); - } - const updatedCart = (await userCart.save()).populate('items.product'); - return updatedCart; + if (indexToUpdate >= 0 && count === 0) { + userCart.items.splice(indexToUpdate, 1); } - // return await getCartResponse(await getCartByIdx(cartIdx)); + const updatedCart = await (await userCart.save()).populate('items.product'); + return getCartResponse(updatedCart); } export const createOrder = async (userId: string, orderInfo: IOrderInfo) => { - const cartIdx = await findUserCartIdx(userId); - if (cartIdx === -1) { - throwEmptyCart(userId); - } - const cart = await getCartByIdx(cartIdx); - if (cart.items.length === 0) { + const userCart = await findUserCartWithProductOrFail(userId); + if (userCart.items.length === 0) { throwEmptyCart(userId); } - const newOrder = generateOrder(cart, orderInfo); - await pushOrder(newOrder); - await updateCartProperty(cartIdx, CartEditableProperties.IsDeleted, true); + const newOrderDTO = generateOrderDTO(userCart, orderInfo); + const newOrder = await OrderModel.create(newOrderDTO); + userCart.isDeleted = true; + await userCart.save(); return newOrder; } diff --git a/src/utils/cartUtils.ts b/src/utils/cartUtils.ts index 75839e5..6012cdf 100644 --- a/src/utils/cartUtils.ts +++ b/src/utils/cartUtils.ts @@ -1,5 +1,7 @@ -import { ICart, ICartItem, ICartResponse, IDeleteCartResponse, OmitCart } from "../scheme/CartScheme"; +import { Types } from "mongoose"; +import { ICart, ICartItem, ICartItemSchema, ICartResponse, ICartSchema, IDeleteCartResponse, OmitCart, PopulatedCart } from "../scheme/CartScheme"; import { IOrderInfo, IOrder, OrderStatuses } from "../scheme/OrderScheme"; +import { IProduct } from "../scheme/ProductScheme"; import { createDeepCopy, getUUID } from "./utils"; export const generateCart = (userId: string): ICart => { @@ -12,11 +14,11 @@ export const generateCart = (userId: string): ICart => { return newCart; } -export const getOmitCart = ({ id, items }: ICart): OmitCart => { - return { id, items }; +export const getOmitCart = ({ _id, items }: PopulatedCart | ICartSchema): OmitCart => { + return { _id, items }; } -export const getTotalPrice = (items: ICartItem[]): number => { +export const getTotalPrice = (items: ICartItem[] | ICartItemSchema[]): number => { let totalPrice = 0; items.map((item) => { const price = item.product.price; @@ -25,7 +27,7 @@ export const getTotalPrice = (items: ICartItem[]): number => { return totalPrice; } -export const getCartResponse = (cart: ICart): ICartResponse => { +export const getCartResponse = (cart: PopulatedCart | ICartSchema): ICartResponse => { const omitCart = getOmitCart(cart); const totalPrice = getTotalPrice(cart.items); const response: ICartResponse = { @@ -48,12 +50,16 @@ export const getDeleteCartResponse = (): IDeleteCartResponse => { return response; } -export const generateOrder = (cart: ICart, orderInfo: IOrderInfo): IOrder => { - const newOrder: IOrder = { - id: getUUID(), - userId: cart.userId, - cartId: cart.id, - items: createDeepCopy(cart.items), +export const generateOrderDTO = (cart: PopulatedCart, orderInfo: IOrderInfo) => { + const newOrderDTO = { + user: cart.user, + cart: cart._id, + items: cart.items.map(item => ({ + product: item.product._id.toString(), + title: item.product.title, + description: item.product.description, + price: item.product.price, + })), payment: { type: orderInfo.payment.type, address: orderInfo.payment.address, @@ -64,8 +70,8 @@ export const generateOrder = (cart: ICart, orderInfo: IOrderInfo): IOrder => { address: orderInfo.delivery.address, }, comments: orderInfo.comments, - status: OrderStatuses.Created, total: 0, } - return newOrder; + newOrderDTO.total = getTotalPrice(cart.items); + return newOrderDTO; } From b915fee3807c3248cb5284118026c6c8bb3aa219 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 3 Nov 2023 00:58:07 +0600 Subject: [PATCH 12/14] Refactor schemas and old logic --- src/repository/cart.repository.ts | 31 +++++++++++++++++--- src/repository/order.repository.ts | 27 +++++++++++++++-- src/scheme/CartScheme.ts | 47 +++++++++++------------------- src/scheme/OrderScheme.ts | 30 +++---------------- src/scheme/ProductScheme.ts | 16 +++++----- src/service/cart.service.ts | 6 ++-- src/utils/cartUtils.ts | 23 ++++----------- 7 files changed, 88 insertions(+), 92 deletions(-) diff --git a/src/repository/cart.repository.ts b/src/repository/cart.repository.ts index 7218331..4dafc3c 100644 --- a/src/repository/cart.repository.ts +++ b/src/repository/cart.repository.ts @@ -1,5 +1,28 @@ import { cartDB } from "../db/db"; -import { CartEditableProperties, ICart, ICartItem } from "../scheme/CartScheme"; +import { IPopulatedCartItem } from "../scheme/CartScheme"; +import { getUUID } from "../utils/utils"; + +interface ICart { + id: string; // uuid + userId: string; + isDeleted: boolean; + items: IPopulatedCartItem[]; +} + +enum CartEditableProperties { + Items = 'items', + IsDeleted = "isDeleted" +} + +export const generateCart = (userId: string): ICart => { + const newCart: ICart = { + id: getUUID(), + userId: userId, + isDeleted: false, + items: [], + } + return newCart; +} export const pushCart = async (cart: ICart) => { await cartDB.push(cart); @@ -20,7 +43,7 @@ export const findUserCartIdxByID = async (userId: string, cartId: string) => { return index; } -export const updateCartProperty = async (cartIdx: number, property: CartEditableProperties, value: boolean | ICartItem[]) => { +export const updateCartProperty = async (cartIdx: number, property: CartEditableProperties, value: boolean | IPopulatedCartItem[]) => { cartDB[cartIdx][property] = value; } @@ -28,11 +51,11 @@ export const getCartByIdx = async (cartIdx: number): Promise => { return await cartDB[cartIdx]; } export const getCartProductIdx = async (cartIdx: number, productId: string) => { - const index = await cartDB[cartIdx].items.findIndex((item: ICartItem) => item.product.id === productId); + const index = await cartDB[cartIdx].items.findIndex((item: IPopulatedCartItem) => item.product.product === productId); return index; } -export const pushCartItem = async (cartIdx: number, product: ICartItem) => { +export const pushCartItem = async (cartIdx: number, product: IPopulatedCartItem) => { await cartDB[cartIdx].items.push(product) } diff --git a/src/repository/order.repository.ts b/src/repository/order.repository.ts index ea45c75..950dc29 100644 --- a/src/repository/order.repository.ts +++ b/src/repository/order.repository.ts @@ -1,11 +1,32 @@ import { orderDB } from "../db/db"; -import { IOrder } from "../scheme/OrderScheme"; +import { IPopulatedCartItem } from "../scheme/CartScheme"; -export const pushOrder = async (order: IOrder): Promise => { +type ORDER_STATUS = 'created' | 'completed'; + +interface IPopulatedOrder { + id: string, + userId: string; + cartId: string; + items: IPopulatedCartItem[] + payment: { + type: string, + address?: unknown, + creditCard?: unknown, + }, + delivery: { + type: string, + address: unknown, + }, + comments: string, + status: ORDER_STATUS; + total: number; +} + +export const pushOrder = async (order: IPopulatedOrder): Promise => { await orderDB.push(order); return orderDB.length - 1; } -export const getOrderByIdx = async (idx: number): Promise => { +export const getOrderByIdx = async (idx: number): Promise => { return await orderDB[idx]; } diff --git a/src/scheme/CartScheme.ts b/src/scheme/CartScheme.ts index 14b6769..714b80e 100644 --- a/src/scheme/CartScheme.ts +++ b/src/scheme/CartScheme.ts @@ -1,60 +1,47 @@ import { Schema, Types, model } from "mongoose"; import { IResponse } from "./ServiceUtils"; import { IProduct } from "./ProductScheme"; -import { Document } from "mongoose"; export interface ICart { - id: string; // uuid - userId: string; + _id: Types.ObjectId; + user: Types.ObjectId; isDeleted: boolean; - items: ICartItem[]; + items: Types.DocumentArray; } +export type PopulatedCart = Omit & { + items: IPopulatedCartItem[]; +}; + export interface ICartItem { - product: IProduct, + product: Types.ObjectId, count: number, } -export interface ICartItemSchema { - product: Types.ObjectId, +export interface ICartItemByID { + productId: string, count: number, } -export interface ICartSchema { - _id: Types.ObjectId; - user: Types.ObjectId; - isDeleted: boolean; - items: Types.DocumentArray; +export interface IPopulatedCartItem { + product: IProduct, + count: number, } -const cartItemSchema = new Schema({ +const cartItemSchema = new Schema({ product: { type: Schema.Types.ObjectId, ref: 'Product', required: true }, count: { type: Number, required: true }, }, { versionKey: false }); -const cartSchema = new Schema({ +const cartSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', required: true, }, isDeleted: { type: Boolean, default: false, required: true }, items: [cartItemSchema], }, { versionKey: false }); -export const CartModel = model('Cart', cartSchema); - -export type PopulatedCart = Omit & { - items: ICartItem[]; -}; +export const CartModel = model('Cart', cartSchema); -export interface ICartItemByID { - productId: string, - count: number, -} - -export type OmitCart = Pick; - -export enum CartEditableProperties { - Items = 'items', - IsDeleted = "isDeleted" -} +export type OmitCart = Pick; export interface ICartResponse extends IResponse { data: { diff --git a/src/scheme/OrderScheme.ts b/src/scheme/OrderScheme.ts index 09294bb..9e6ebfa 100644 --- a/src/scheme/OrderScheme.ts +++ b/src/scheme/OrderScheme.ts @@ -1,31 +1,10 @@ import { Schema, Types, model } from "mongoose"; -import { ICartItem } from "./CartScheme"; - -type ORDER_STATUS = 'created' | 'completed'; +import { IPopulatedCartItem } from "./CartScheme"; export interface IOrder { - id: string, - userId: string; - cartId: string; - items: ICartItem[] - payment: { - type: string, - address?: unknown, - creditCard?: unknown, - }, - delivery: { - type: string, - address: unknown, - }, - comments: string, - status: ORDER_STATUS; - total: number; -} - -export interface IOrderSchema { user: Types.ObjectId; cart: Types.ObjectId; - items: ICartItem[]; + items: IPopulatedCartItem[]; payment: { type: string, address?: unknown, @@ -45,7 +24,7 @@ export enum OrderStatuses { Complited = "completed" } -const orderSchema = new Schema({ +const orderSchema = new Schema({ user: { type: Schema.Types.ObjectId, ref: 'User', required: true }, cart: { type: Schema.Types.ObjectId, ref: 'Cart', required: true }, items: { @@ -70,7 +49,6 @@ const orderSchema = new Schema({ total: { type: Number, required: true }, }, { versionKey: false }); -export const OrderModel = model('Order', orderSchema); - +export const OrderModel = model('Order', orderSchema); export type IOrderInfo = Pick; diff --git a/src/scheme/ProductScheme.ts b/src/scheme/ProductScheme.ts index 1ccc44d..8b3954b 100644 --- a/src/scheme/ProductScheme.ts +++ b/src/scheme/ProductScheme.ts @@ -9,14 +9,6 @@ export interface IProduct { product?: string; } -export interface IProductResponse extends IResponse { - data: IProduct, -} - -export interface IProductsResponse extends IResponse { - data: IProduct[], -} - const productSchema = new Schema({ title: { type: String, required: true }, description: { type: String, required: true }, @@ -25,3 +17,11 @@ const productSchema = new Schema({ }, { versionKey: false }); export const ProductModel = model("Product", productSchema); + +export interface IProductResponse extends IResponse { + data: IProduct, +} + +export interface IProductsResponse extends IResponse { + data: IProduct[], +} diff --git a/src/service/cart.service.ts b/src/service/cart.service.ts index 36c03c7..5ea8032 100644 --- a/src/service/cart.service.ts +++ b/src/service/cart.service.ts @@ -1,11 +1,11 @@ -import { CartModel, ICartItem, ICartItemByID, ICartResponse, IDeleteCartResponse } from "../scheme/CartScheme"; +import { CartModel, IPopulatedCartItem, ICartItemByID, ICartResponse, IDeleteCartResponse } from "../scheme/CartScheme"; import { IOrderInfo, OrderModel } from "../scheme/OrderScheme"; import { ProductModel } from "../scheme/ProductScheme"; import { generateOrderDTO, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; import { throwEmptyCart, throwNoCartExists, throwNoProductExists } from "../utils/errors"; const findActiveUserCart = async (userId: string) => { - return await CartModel.findOne({ user: userId, isDeleted: false }).populate<{ items: ICartItem[] }>('items.product'); + return await CartModel.findOne({ user: userId, isDeleted: false }).populate<{ items: IPopulatedCartItem[] }>('items.product'); } const findActiveUserCartOrFail = async (userId: string) => { return await CartModel.findOne({ user: userId, isDeleted: false }).orFail(() => throwNoCartExists(userId)); @@ -13,7 +13,7 @@ const findActiveUserCartOrFail = async (userId: string) => { const findUserCartWithProductOrFail = async (userId: string) => { return await CartModel - .findOne({ user: userId, isDeleted: false }).populate<{ items: ICartItem[] }>('items.product') + .findOne({ user: userId, isDeleted: false }).populate<{ items: IPopulatedCartItem[] }>('items.product') .orFail(() => throwNoCartExists(userId)); } diff --git a/src/utils/cartUtils.ts b/src/utils/cartUtils.ts index 6012cdf..0b7b35d 100644 --- a/src/utils/cartUtils.ts +++ b/src/utils/cartUtils.ts @@ -1,24 +1,11 @@ -import { Types } from "mongoose"; -import { ICart, ICartItem, ICartItemSchema, ICartResponse, ICartSchema, IDeleteCartResponse, OmitCart, PopulatedCart } from "../scheme/CartScheme"; -import { IOrderInfo, IOrder, OrderStatuses } from "../scheme/OrderScheme"; -import { IProduct } from "../scheme/ProductScheme"; -import { createDeepCopy, getUUID } from "./utils"; +import { IPopulatedCartItem, ICartItem, ICartResponse, ICart, IDeleteCartResponse, OmitCart, PopulatedCart } from "../scheme/CartScheme"; +import { IOrderInfo } from "../scheme/OrderScheme"; -export const generateCart = (userId: string): ICart => { - const newCart: ICart = { - id: getUUID(), - userId: userId, - isDeleted: false, - items: [], - } - return newCart; -} - -export const getOmitCart = ({ _id, items }: PopulatedCart | ICartSchema): OmitCart => { +export const getOmitCart = ({ _id, items }: PopulatedCart | ICart): OmitCart => { return { _id, items }; } -export const getTotalPrice = (items: ICartItem[] | ICartItemSchema[]): number => { +export const getTotalPrice = (items: IPopulatedCartItem[] | ICartItem[]): number => { let totalPrice = 0; items.map((item) => { const price = item.product.price; @@ -27,7 +14,7 @@ export const getTotalPrice = (items: ICartItem[] | ICartItemSchema[]): number => return totalPrice; } -export const getCartResponse = (cart: PopulatedCart | ICartSchema): ICartResponse => { +export const getCartResponse = (cart: PopulatedCart | ICart): ICartResponse => { const omitCart = getOmitCart(cart); const totalPrice = getTotalPrice(cart.items); const response: ICartResponse = { From 7cd409c4063c0f88905c85ec53dfe7c1a892926d Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 3 Nov 2023 01:28:36 +0600 Subject: [PATCH 13/14] Fix cart response logic --- src/service/cart.service.ts | 9 +++++---- src/utils/cartUtils.ts | 16 +++++++++++----- src/utils/errors.ts | 2 -- src/utils/utils.ts | 1 - 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/service/cart.service.ts b/src/service/cart.service.ts index 5ea8032..b0567a6 100644 --- a/src/service/cart.service.ts +++ b/src/service/cart.service.ts @@ -1,7 +1,7 @@ import { CartModel, IPopulatedCartItem, ICartItemByID, ICartResponse, IDeleteCartResponse } from "../scheme/CartScheme"; import { IOrderInfo, OrderModel } from "../scheme/OrderScheme"; import { ProductModel } from "../scheme/ProductScheme"; -import { generateOrderDTO, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; +import { generateOrderDTO, getCartResponse, getDeleteCartResponse, getPopulatedCartResponse } from "../utils/cartUtils"; import { throwEmptyCart, throwNoCartExists, throwNoProductExists } from "../utils/errors"; const findActiveUserCart = async (userId: string) => { @@ -20,7 +20,7 @@ const findUserCartWithProductOrFail = async (userId: string) => { export const createOrGetCart = async (userId: string): Promise => { const userCart = await findActiveUserCart(userId); if (userCart) { - return getCartResponse(userCart); + return getPopulatedCartResponse(userCart); } const newCart = await CartModel.create({ user: userId }); return getCartResponse(newCart); @@ -51,8 +51,9 @@ export const updateCart = async (userId: string, { productId, count }: ICartItem if (indexToUpdate >= 0 && count === 0) { userCart.items.splice(indexToUpdate, 1); } - const updatedCart = await (await userCart.save()).populate('items.product'); - return getCartResponse(updatedCart); + await userCart.save(); + const updatedCart = await findUserCartWithProductOrFail(userId); + return getPopulatedCartResponse(updatedCart); } export const createOrder = async (userId: string, orderInfo: IOrderInfo) => { diff --git a/src/utils/cartUtils.ts b/src/utils/cartUtils.ts index 0b7b35d..d66e008 100644 --- a/src/utils/cartUtils.ts +++ b/src/utils/cartUtils.ts @@ -1,14 +1,14 @@ -import { IPopulatedCartItem, ICartItem, ICartResponse, ICart, IDeleteCartResponse, OmitCart, PopulatedCart } from "../scheme/CartScheme"; +import { IPopulatedCartItem, ICartResponse, ICart, IDeleteCartResponse, OmitCart, PopulatedCart } from "../scheme/CartScheme"; import { IOrderInfo } from "../scheme/OrderScheme"; export const getOmitCart = ({ _id, items }: PopulatedCart | ICart): OmitCart => { return { _id, items }; } -export const getTotalPrice = (items: IPopulatedCartItem[] | ICartItem[]): number => { +export const getTotalPrice = (items: IPopulatedCartItem[]): number => { let totalPrice = 0; items.map((item) => { - const price = item.product.price; + const price = item.product.price totalPrice += price * item.count; }); return totalPrice; @@ -16,17 +16,23 @@ export const getTotalPrice = (items: IPopulatedCartItem[] | ICartItem[]): number export const getCartResponse = (cart: PopulatedCart | ICart): ICartResponse => { const omitCart = getOmitCart(cart); - const totalPrice = getTotalPrice(cart.items); const response: ICartResponse = { data: { cart: omitCart, - totalPrice: totalPrice + totalPrice: 0 }, error: null, } return response; } +export const getPopulatedCartResponse = (cart: PopulatedCart): ICartResponse => { + const response = getCartResponse(cart); + const totalPrice = getTotalPrice(cart.items); + response.data.totalPrice = totalPrice; + return response; +} + export const getDeleteCartResponse = (): IDeleteCartResponse => { const response: IDeleteCartResponse = { data: { diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 620d5d4..9832f11 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -10,13 +10,11 @@ interface IResponseError extends Error { status: number; } - const getResposeError = (message: string, status: number): ResponseError => { const err = new ResponseError(message, status); return err; } - export const throwNoUser = (id: string) => { throw new Error(`No user ${id}`); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 7d7e611..5662690 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -11,7 +11,6 @@ export const createDeepCopy = (arr: T[]): T[] => { return cloneDeep(arr); } - export const promiseHandler = async (prom: Promise, next: NextFunction) => { try { return await prom; From a36a2bebac79937952d1540e1e595bc879b62653 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Thu, 2 Nov 2023 23:34:30 +0400 Subject: [PATCH 14/14] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index aa0524a..947daac 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ One User can have multiple Order. Each Order **Setup for startring (once)** + In the project root folder - Install [podman](https://podman.io/docs/installation) @@ -59,3 +60,8 @@ In the project root folder **First time starting** - Create your first database in MongoDB. Then, create your first collection (Example - User). + +**Results** + +![image](https://github.com/kandalova/node-mentoring-shop/assets/26093763/9e525a2f-c86b-4ecd-9f4e-5f8fe95a6904) +