From d09b7616733fc2d45c4f07a6bc78e02b9668108b Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 6 Oct 2023 21:53:03 +0600 Subject: [PATCH 01/17] Two working methods --- .eslintrc.js | 2 + .vscode/launch.json | 26 + package-lock.json | 754 ++++++++++++++++++++++++++++- package.json | 8 + src/controller/cart.controller.ts | 26 + src/db/cart.db.js | 1 + src/db/order.db.js | 1 + src/db/product.db.js | 38 ++ src/db/user.db.js | 6 + src/index.ts | 47 +- src/repository/cart.repository.ts | 30 ++ src/repository/order.repository.ts | 11 + src/repository/user.repository.ts | 5 + src/scheme/CartScheme.ts | 34 ++ src/scheme/OrderScheme.ts | 30 ++ src/scheme/ProductScheme.ts | 6 + src/scheme/ServiceUtils.ts | 12 + src/scheme/UserScheme.ts | 3 + src/service/cart.service.ts | 66 +++ src/service/product.service.ts | 9 + src/utils/cartUtils.ts | 71 +++ src/utils/errors.ts | 54 +++ src/utils/utils.ts | 37 ++ tsconfig.json | 6 +- 24 files changed, 1276 insertions(+), 7 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/controller/cart.controller.ts create mode 100644 src/db/cart.db.js create mode 100644 src/db/order.db.js create mode 100644 src/db/product.db.js create mode 100644 src/db/user.db.js create mode 100644 src/repository/cart.repository.ts create mode 100644 src/repository/order.repository.ts create mode 100644 src/repository/user.repository.ts create mode 100644 src/scheme/CartScheme.ts create mode 100644 src/scheme/OrderScheme.ts create mode 100644 src/scheme/ProductScheme.ts create mode 100644 src/scheme/ServiceUtils.ts create mode 100644 src/scheme/UserScheme.ts create mode 100644 src/service/cart.service.ts create mode 100644 src/service/product.service.ts create mode 100644 src/utils/cartUtils.ts create mode 100644 src/utils/errors.ts create mode 100644 src/utils/utils.ts diff --git a/.eslintrc.js b/.eslintrc.js index 25ca5b9..b6a36b6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -30,5 +30,7 @@ module.exports = { "@typescript-eslint" ], "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }], } } diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..9e34be8 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "command": "npm start", + "name": "Run npm start", + "request": "launch", + "type": "node-terminal" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}\\src\\index.ts", + "outFiles": [ + "${workspaceFolder}/**/*.js" + ] + } + ] +} diff --git a/package-lock.json b/package-lock.json index d3b94ea..43ced24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,16 @@ "name": "node-mentoring-shop", "version": "1.0.0", "license": "ISC", + "dependencies": { + "express": "^4.18.2", + "lodash.clonedeep": "^4.5.0", + "uuid": "^9.0.1" + }, "devDependencies": { + "@types/express": "^4.17.18", + "@types/lodash.clonedeep": "^4.5.7", "@types/node": "^20.8.2", + "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "eslint": "^8.50.0", @@ -258,24 +266,133 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/body-parser": { + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", + "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.36", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", + "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.18", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.18.tgz", + "integrity": "sha512-Sxv8BSLLgsBYmcnGdGjjEjqET2U+AKAdCRODmMiq02FgjwuV75Ut85DRpvFjyw/Mk0vgUOliGRU0UUmuuZHByQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.37", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz", + "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.13", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.199", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz", + "integrity": "sha512-Vrjz5N5Ia4SEzWWgIVwnHNEnb1UE1XMkvY5DGXrAeOGE9imk0hgTHh5GyDjLDJi9OTCn9oo9dXH1uToK1VRfrg==", + "dev": true + }, + "node_modules/@types/lodash.clonedeep": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.7.tgz", + "integrity": "sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mime": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==", + "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 }, + "node_modules/@types/qs": { + "version": "6.9.8", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", + "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", + "dev": true + }, "node_modules/@types/semver": { "version": "7.5.3", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.3.tgz", "integrity": "sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.2", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", + "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", + "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/uuid": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.4.tgz", + "integrity": "sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==", + "dev": true + }, "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", @@ -563,6 +680,18 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -658,6 +787,11 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -682,6 +816,42 @@ "node": ">=8" } }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -704,6 +874,26 @@ "node": ">=8" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -801,6 +991,38 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -836,6 +1058,23 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -869,6 +1108,24 @@ "node": ">=6.0.0" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1057,6 +1314,68 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -1124,6 +1443,36 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -1160,6 +1509,22 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1180,6 +1545,25 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -1253,6 +1637,14 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -1262,6 +1654,54 @@ "node": ">=4" } }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -1315,8 +1755,15 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -1442,6 +1889,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -1466,6 +1918,19 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -1475,6 +1940,14 @@ "node": ">= 8" } }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -1488,6 +1961,36 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1503,8 +2006,7 @@ "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==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -1512,6 +2014,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/nodemon": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.0.1.tgz", @@ -1564,6 +2074,25 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1632,6 +2161,14 @@ "node": ">=6" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1659,6 +2196,11 @@ "node": ">=8" } }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -1689,6 +2231,18 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -1704,6 +2258,20 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1724,6 +2292,28 @@ } ] }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1793,6 +2383,30 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -1808,6 +2422,61 @@ "node": ">=10" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -1829,6 +2498,19 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -1850,6 +2532,14 @@ "node": ">=8" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -1904,6 +2594,14 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -1995,6 +2693,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", @@ -2014,6 +2724,14 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2023,12 +2741,40 @@ "punycode": "^2.1.0" } }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/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 b3d0f38..a2b994f 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,20 @@ "author": "", "license": "ISC", "devDependencies": { + "@types/express": "^4.17.18", + "@types/lodash.clonedeep": "^4.5.7", "@types/node": "^20.8.2", + "@types/uuid": "^9.0.4", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", "eslint": "^8.50.0", "nodemon": "^3.0.1", "ts-node": "^10.9.1", "typescript": "^5.2.2" + }, + "dependencies": { + "express": "^4.18.2", + "lodash.clonedeep": "^4.5.0", + "uuid": "^9.0.1" } } diff --git a/src/controller/cart.controller.ts b/src/controller/cart.controller.ts new file mode 100644 index 0000000..8a73c6f --- /dev/null +++ b/src/controller/cart.controller.ts @@ -0,0 +1,26 @@ +import express, { NextFunction, Request, Response } from "express"; +import { deleteCart, getCart, postCart } from "../service/cart.service"; +import { promiseHandler } from "../utils/utils"; + +const cartRouter = express.Router(); +cartRouter.use(express.json()); + +cartRouter.post("/", async (req:Request, res:Response, next:NextFunction) => { + const userId = req.header('x-user-id')!; + const cart = await promiseHandler(postCart(userId), next); + res.status(201).send(cart); +}); + +cartRouter.get("/", async (req:Request, res:Response, next:NextFunction) => { + const userId = req.header('x-user-id')!; + const cart = await promiseHandler(getCart(userId), next); + res.send(cart); +}); + +cartRouter.delete("/", async (req:Request, res:Response, next:NextFunction) => { + const userId = req.header('x-user-id')!; + const data = await promiseHandler(deleteCart(userId), next); + res.send(data); +}); + +export default cartRouter; diff --git a/src/db/cart.db.js b/src/db/cart.db.js new file mode 100644 index 0000000..263d7f4 --- /dev/null +++ b/src/db/cart.db.js @@ -0,0 +1 @@ +export const carts = []; diff --git a/src/db/order.db.js b/src/db/order.db.js new file mode 100644 index 0000000..abdacfb --- /dev/null +++ b/src/db/order.db.js @@ -0,0 +1 @@ +export const orders=[]; diff --git a/src/db/product.db.js b/src/db/product.db.js new file mode 100644 index 0000000..20e4e18 --- /dev/null +++ b/src/db/product.db.js @@ -0,0 +1,38 @@ +export const products = [ + { + id: '32c985f8-ba49-42d1-aa7a-5f0ba24d0354', + title: 'PUMA', + description: ' Carina Street Mid PUMA Black', + price: 203.4, + }, + { + id: '2f592419-bb89-475e-b3fe-c7ecadaf2856', + title: 'Reebok', + description: 'REEBOK COURT ADVANCE', + price: 202.4, + }, + { + id: 'b7924f9e-98c1-463f-8dfd-677c1bd22bd6', + title: 'adidas Originals', + description: 'TREZIOD 2', + price: 305, + }, + { + id: '2a9d4f30-93df-4f86-aa10-5a45aece7ebf', + title: 'adidas Originals', + description: 'GAZELLE', + price: 139, + }, + { + id: '38ecc31a-bce8-46f7-bdfd-3a320bd3c157', + title: 'Reebok', + description: 'SMASH EDGE S', + price: 139, + }, + { + id: 'fb0bc7a1-a731-4a2e-ad12-2478a9e3fd82', + title: 'Vans', + description: 'UA SK8-HI MTE-1', + price: 173.9, + } +] diff --git a/src/db/user.db.js b/src/db/user.db.js new file mode 100644 index 0000000..558bc33 --- /dev/null +++ b/src/db/user.db.js @@ -0,0 +1,6 @@ +export const users = [ + '99c27b1a-0ae2-486d-9d72-9c82579cf361', + 'ed3d57d6-c699-4909-910a-364745332f88', + 'e73b79aa-9cb0-4406-97fd-df231243beec', + 'cf35167a-574d-49e3-a0ed-affd8d746d6c', +] diff --git a/src/index.ts b/src/index.ts index b9d3e23..53a1e74 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,46 @@ -console.log('Hello world!'); +import {users} from "./db/user.db.js" +import {products} from "./db/product.db.js" +import {carts} from "./db/cart.db.js" +import {orders} from "./db/order.db.js" +import express, { NextFunction, Request, Response } from "express"; +import { findUser } from "./repository/user.repository.ts"; +import cartRouter from "./controller/cart.controller.ts"; +import { ResponseError, getForbidenError, getUnauthorizedError } from "./utils/errors.ts"; +import { getResponseError } from "./utils/utils.ts"; + +export const userDB = users; +export const productDB = products; +export const cartDB = carts; +export const orderDB = orders; + +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()); + } + const user = await findUser(userId); + if(!user){ + next(getUnauthorizedError()); + } + next(); +} + +const errorHandler = async (err:Error|ResponseError, _req:Request, res: Response, _next: NextFunction) => { + if(err instanceof ResponseError){ + res.status(err.status); + } + else{ + res.status(500); + } + res.send(await getResponseError(err.message)); +} + +app.use('/api/profile/cart', headerHandler, cartRouter, errorHandler); + + +app.listen(PORT, () => { + console.log(`Server is Fire at http://localhost:${PORT}`); +}); diff --git a/src/repository/cart.repository.ts b/src/repository/cart.repository.ts new file mode 100644 index 0000000..6e07018 --- /dev/null +++ b/src/repository/cart.repository.ts @@ -0,0 +1,30 @@ +import { cartDB } from ".."; +import { CartEditableProperties, ICart, ICartItem } from "../scheme/CartScheme"; + +export const pushCart = async (cart:ICart)=>{ + await cartDB.push(cart); +} + +export const findUserCart = async (userId: string):Promise=>{ + const cart = await cartDB.find((cart) => cart.userId === userId && !cart.isDeleted); + return cart; +} + +export const findUserCartIdx = async (userId: string)=>{ + const index = await cartDB.findIndex((cart) => cart.userId === userId && !cart.isDeleted); + return index; +} + + +export const findUserCartIdxByID = async(userId: string, cartId:string)=>{ + const index = await cartDB.findIndex((cart) => cart.userId === userId && cart.id === cartId && !cart.isDeleted); + return index; +} + +export const updateCartProperty = async(cartIdx:number, property:CartEditableProperties, value:boolean|ICartItem[])=>{ + cartDB[cartIdx][property] = value; +} + +export const getCartByIdx =async (cartIdx:number):Promise => { + return await cartDB[cartIdx]; +} diff --git a/src/repository/order.repository.ts b/src/repository/order.repository.ts new file mode 100644 index 0000000..11190a6 --- /dev/null +++ b/src/repository/order.repository.ts @@ -0,0 +1,11 @@ +import { orderDB } from ".."; +import { IOrder } from "../scheme/OrderScheme"; + +export const pushOrder = async (order:IOrder):Promise =>{ + await orderDB.push(order); + return orderDB.length-1; +} + +export const getOrderByIdx = async (idx:number):Promise =>{ + return await orderDB[idx]; +} diff --git a/src/repository/user.repository.ts b/src/repository/user.repository.ts new file mode 100644 index 0000000..f379efe --- /dev/null +++ b/src/repository/user.repository.ts @@ -0,0 +1,5 @@ +import { userDB } from ".."; + +export const findUser = async (userId:string)=>{ + return await userDB.find((id) => id === userId); +} diff --git a/src/scheme/CartScheme.ts b/src/scheme/CartScheme.ts new file mode 100644 index 0000000..872e96a --- /dev/null +++ b/src/scheme/CartScheme.ts @@ -0,0 +1,34 @@ +import { IProduct } from "./ProductScheme"; +import { IResponse } from "./ServiceUtils"; + +export interface ICart{ + id: string; // uuid + userId: string; + isDeleted: boolean; + items: ICartItem[]; +} + +export interface ICartItem{ + product: IProduct, + count: number, +} + +export type OmitCart = Omit; + +export enum CartEditableProperties { + Items = 'items', + IsDeleted = "isDeleted" +} + +export interface ICartResponse extends IResponse{ + data: { + cart: OmitCart, + totalPrice: number, + }, +} + +export interface IDeleteCartResponse extends IResponse{ + data: { + success: boolean + }, +} diff --git a/src/scheme/OrderScheme.ts b/src/scheme/OrderScheme.ts new file mode 100644 index 0000000..8a9f2bb --- /dev/null +++ b/src/scheme/OrderScheme.ts @@ -0,0 +1,30 @@ +import { ICartItem } from "./CartScheme"; + +type ORDER_STATUS = 'created' | 'completed'; + +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 type IOrderInfo = Pick; + +export enum OrderStatuses { + Created = 'created', + Complited = "completed" +} diff --git a/src/scheme/ProductScheme.ts b/src/scheme/ProductScheme.ts new file mode 100644 index 0000000..78cc332 --- /dev/null +++ b/src/scheme/ProductScheme.ts @@ -0,0 +1,6 @@ +export interface IProduct{ + id: string; + title: string; + description: string; + price: number; +} diff --git a/src/scheme/ServiceUtils.ts b/src/scheme/ServiceUtils.ts new file mode 100644 index 0000000..f2e6b8d --- /dev/null +++ b/src/scheme/ServiceUtils.ts @@ -0,0 +1,12 @@ +export interface IResponseError{ + data:null, + error: { + message: string + }, +} + +export interface IResponse{ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + data:any, + error: null, +} diff --git a/src/scheme/UserScheme.ts b/src/scheme/UserScheme.ts new file mode 100644 index 0000000..ca7aa5e --- /dev/null +++ b/src/scheme/UserScheme.ts @@ -0,0 +1,3 @@ +export interface IUser{ + id: string, +} diff --git a/src/service/cart.service.ts b/src/service/cart.service.ts new file mode 100644 index 0000000..a1a1f07 --- /dev/null +++ b/src/service/cart.service.ts @@ -0,0 +1,66 @@ +import { findUserCart, findUserCartIdx, findUserCartIdxByID, getCartByIdx, pushCart, updateCartProperty } from "../repository/cart.repository"; +import { pushOrder } from "../repository/order.repository"; +import { CartEditableProperties, ICartResponse, IDeleteCartResponse, OmitCart } from "../scheme/CartScheme"; +import { IOrderInfo } from "../scheme/OrderScheme"; +import { generateCart, generateOrder, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; +import { getNoCartExists, throwCartExistsError, throwEmptyCart, throwNoCartExists, throwNoCartExistsById } from "../utils/errors"; + + +const createCart = async (userId: string, isGetOrCreate = false): Promise => { + const userCart = await findUserCart(userId); + if (userCart && isGetOrCreate) { + return getCartResponse(userCart); + } + if (userCart && !isGetOrCreate) { + throwCartExistsError(userId); + } + const newCart = generateCart(userId); + await pushCart(newCart); + return getCartResponse(newCart); +}; + +export const deleteCart2 = async (userId: string): Promise => { + // return new Promise(async (res, rej) => { + const cartIdx = await findUserCartIdx(userId); + if (cartIdx === -1) { + throwNoCartExists(userId); + } + await updateCartProperty(cartIdx, CartEditableProperties.IsDeleted, true); + const response = getDeleteCartResponse(); + return response; + + // }) +} + +export const deleteCart = async (userId: string): Promise => { + return deleteCart2(userId); +}; + +export const postCart = async (userId: string): Promise => { + return createCart(userId); +}; + +export const getCart = async (userId: string): Promise => { + return createCart(userId, true); +} + +export const updateCart = async (userId: string, updatedCart: OmitCart): Promise => { + const cartIdx = await findUserCartIdxByID(userId, updatedCart.id); + if (cartIdx === -1) { + throwNoCartExistsById(userId, updatedCart.id); + } + await updateCartProperty(cartIdx, CartEditableProperties.Items, updatedCart.items); + return await getCartByIdx(cartIdx); +} + +export const createOrder = async (userId: string, orderInfo: IOrderInfo) => { + const cartIdx = await findUserCartIdx(userId); + if (cartIdx === -1) { + throwEmptyCart(userId); + } + const cart = await getCartByIdx(cartIdx); + const newOrder = generateOrder(cart, orderInfo); + await pushOrder(newOrder); + await updateCartProperty(cartIdx, CartEditableProperties.IsDeleted, true); + return newOrder; +} diff --git a/src/service/product.service.ts b/src/service/product.service.ts new file mode 100644 index 0000000..25f3ccf --- /dev/null +++ b/src/service/product.service.ts @@ -0,0 +1,9 @@ +import { IProduct } from "../scheme/ProductScheme"; + +export const getTotalPrice = (products: IProduct[]):number =>{ + let result = 0; + products.map(product=>{ + result=+product.price; + }); + return result; +} diff --git a/src/utils/cartUtils.ts b/src/utils/cartUtils.ts new file mode 100644 index 0000000..75839e5 --- /dev/null +++ b/src/utils/cartUtils.ts @@ -0,0 +1,71 @@ +import { ICart, ICartItem, ICartResponse, IDeleteCartResponse, OmitCart } from "../scheme/CartScheme"; +import { IOrderInfo, IOrder, OrderStatuses } from "../scheme/OrderScheme"; +import { createDeepCopy, getUUID } from "./utils"; + +export const generateCart = (userId: string): ICart => { + const newCart: ICart = { + id: getUUID(), + userId: userId, + isDeleted: false, + items: [], + } + return newCart; +} + +export const getOmitCart = ({ id, items }: ICart): OmitCart => { + return { id, items }; +} + +export const getTotalPrice = (items: ICartItem[]): number => { + let totalPrice = 0; + items.map((item) => { + const price = item.product.price; + totalPrice += price * item.count; + }); + return totalPrice; +} + +export const getCartResponse = (cart: ICart): ICartResponse => { + const omitCart = getOmitCart(cart); + const totalPrice = getTotalPrice(cart.items); + const response: ICartResponse = { + data: { + cart: omitCart, + totalPrice: totalPrice + }, + error: null, + } + return response; +} + +export const getDeleteCartResponse = (): IDeleteCartResponse => { + const response: IDeleteCartResponse = { + data: { + success: true, + }, + error: null, + } + 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), + payment: { + type: orderInfo.payment.type, + address: orderInfo.payment.address, + creditCard: orderInfo.payment.creditCard, + }, + delivery: { + type: orderInfo.delivery.type, + address: orderInfo.delivery.address, + }, + comments: orderInfo.comments, + status: OrderStatuses.Created, + total: 0, + } + return newOrder; +} diff --git a/src/utils/errors.ts b/src/utils/errors.ts new file mode 100644 index 0000000..c2994f6 --- /dev/null +++ b/src/utils/errors.ts @@ -0,0 +1,54 @@ +export class ResponseError extends Error implements IResponseError { + status: number; + constructor(message:string, status:number) { + super(message); + this.status = status; + } +} + +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}`); +} + +export const throwCartExistsError = (id: string):ResponseError => { + throw new ResponseError(`Cart for user ${id} already exists`, 400); +} + +export const throwNoCartExistsById = (id: string, cartId: string) => { + throw new Error(`No cart ${cartId} for user ${id}`); +} + +export const throwNoCartExists = (id: string) => { + throw new ResponseError(`No cart for user ${id}`, 400); +} + +export const getNoCartExists = (id: string):ResponseError => { + return new ResponseError(`No cart for user ${id}`, 400); +} + +export const throwEmptyCart = (id: string) => { + throw new Error(`Cart for user ${id} is empty`); +} + +export const throwUnauthorized = (status=401) => { + throw getResposeError(`Header x-user-id is missing or no user with such id`, status) +} + +export const getUnauthorizedError = (status=401) => { + return getResposeError(`Header x-user-id is missing or no user with such id`, status) +} + +export const getForbidenError = (status=403) => { + return getResposeError(`You must be authorized user`, status) +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts new file mode 100644 index 0000000..d79fc37 --- /dev/null +++ b/src/utils/utils.ts @@ -0,0 +1,37 @@ +import { v4 as uuid } from 'uuid'; +import cloneDeep from 'lodash.clonedeep'; +import { NextFunction } from 'express'; +import { IResponse, IResponseError } from '../scheme/ServiceUtils'; + +export const getUUID = ()=>{ + return uuid(); +}; + +export const createDeepCopy = (arr: T[]): T[] =>{ + return cloneDeep(arr); +} + + +export const promiseHandler = async (prom: Promise, next:NextFunction)=>{ + try { + return await prom; + } catch (error) { + next(error); + } +} + +export const getResponseError = async (message:string):Promise=>{ + return { + data:null, + error:{ + message: message + } + } +} + +export const getResponse = ():IResponse=>{ + return { + data:"", + error:null + } +} diff --git a/tsconfig.json b/tsconfig.json index 230cc40..ad21052 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,9 @@ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "strict": true, /* Enable all strict type-checking options. */ - "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ /* Skip type checking .d.ts files that are included with TypeScript. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "allowImportingTsExtensions": true, + "noEmit": true } } From 2b7fa215a94527b983aaad46f27afa8beb6dd202 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Sat, 7 Oct 2023 00:35:43 +0600 Subject: [PATCH 02/17] Another routes for cart --- src/controller/cart.controller.ts | 57 ++++++++++++++++++++++------- src/repository/cart.repository.ts | 19 +++++++++- src/scheme/CartScheme.ts | 5 +++ src/service/cart.service.ts | 60 ++++++++++++++++++------------- src/service/product.service.ts | 7 ++++ src/utils/errors.ts | 4 +++ src/utils/utils.ts | 2 ++ 7 files changed, 117 insertions(+), 37 deletions(-) diff --git a/src/controller/cart.controller.ts b/src/controller/cart.controller.ts index 8a73c6f..d225923 100644 --- a/src/controller/cart.controller.ts +++ b/src/controller/cart.controller.ts @@ -1,26 +1,59 @@ import express, { NextFunction, Request, Response } from "express"; -import { deleteCart, getCart, postCart } from "../service/cart.service"; +import { createCart, createOrder, deleteCart, updateCart } from "../service/cart.service"; import { promiseHandler } from "../utils/utils"; const cartRouter = express.Router(); cartRouter.use(express.json()); -cartRouter.post("/", async (req:Request, res:Response, next:NextFunction) => { - const userId = req.header('x-user-id')!; - const cart = await promiseHandler(postCart(userId), next); - res.status(201).send(cart); -}); +// 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) => { - const userId = req.header('x-user-id')!; - const cart = await promiseHandler(getCart(userId), next); - res.send(cart); + try{ + const userId = req.header('x-user-id')!; + const cart = await createCart(userId, true); + res.send(cart); + } + catch(err){ + next(err); + } + }); cartRouter.delete("/", async (req:Request, res:Response, next:NextFunction) => { - const userId = req.header('x-user-id')!; - const data = await promiseHandler(deleteCart(userId), next); - res.send(data); + try{ + const userId = req.header('x-user-id')!; + const data = await deleteCart(userId); + res.send(data); + } + catch(err){ + next(err); + } +}); + +cartRouter.post("/checkout", async (req:Request, res:Response, next:NextFunction) => { + try{ + const userId = req.header('x-user-id')!; + const order = await createOrder(userId, req.body); + res.send(order); + } + catch(err){ + next(err); + } +}); + +cartRouter.put("/", async (req:Request, res:Response, next:NextFunction) => { + try{ + const userId = req.header('x-user-id')!; + const order = await updateCart(userId, req.body); + res.send(order); + } + catch(err){ + next(err); + } }); export default cartRouter; diff --git a/src/repository/cart.repository.ts b/src/repository/cart.repository.ts index 6e07018..97518d1 100644 --- a/src/repository/cart.repository.ts +++ b/src/repository/cart.repository.ts @@ -15,7 +15,6 @@ export const findUserCartIdx = async (userId: string)=>{ return index; } - export const findUserCartIdxByID = async(userId: string, cartId:string)=>{ const index = await cartDB.findIndex((cart) => cart.userId === userId && cart.id === cartId && !cart.isDeleted); return index; @@ -28,3 +27,21 @@ export const updateCartProperty = async(cartIdx:number, property:CartEditablePr 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); + return index; +} + +export const pushCartItem =async (cartIdx:number, product:ICartItem) => { + await cartDB[cartIdx].items.push(product) +} + +export const updateCartItemCount =async (cartIdx:number, productIdx:number, count:number) => { + const cartItem =cartDB[cartIdx].items[productIdx]; + cartItem.count = count; +} + +export const deleteCartItem =async (cartIdx:number, productIdx:number) => { + const cartItems =cartDB[cartIdx].items; + await cartItems.splice(productIdx, 1); +} diff --git a/src/scheme/CartScheme.ts b/src/scheme/CartScheme.ts index 872e96a..baf8d37 100644 --- a/src/scheme/CartScheme.ts +++ b/src/scheme/CartScheme.ts @@ -13,6 +13,11 @@ export interface ICartItem{ count: number, } +export interface ICartItemByID{ + productId: string, + count: number, +} + export type OmitCart = Omit; export enum CartEditableProperties { diff --git a/src/service/cart.service.ts b/src/service/cart.service.ts index a1a1f07..05c0c91 100644 --- a/src/service/cart.service.ts +++ b/src/service/cart.service.ts @@ -1,12 +1,13 @@ -import { findUserCart, findUserCartIdx, findUserCartIdxByID, getCartByIdx, pushCart, updateCartProperty } from "../repository/cart.repository"; +import { deleteCartItem, findUserCart, findUserCartIdx, getCartByIdx, getCartProductIdx, pushCart, pushCartItem, updateCartItemCount, updateCartProperty } from "../repository/cart.repository"; import { pushOrder } from "../repository/order.repository"; -import { CartEditableProperties, ICartResponse, IDeleteCartResponse, OmitCart } from "../scheme/CartScheme"; +import { CartEditableProperties, ICartItemByID, ICartResponse, IDeleteCartResponse } from "../scheme/CartScheme"; import { IOrderInfo } from "../scheme/OrderScheme"; import { generateCart, generateOrder, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; -import { getNoCartExists, throwCartExistsError, throwEmptyCart, throwNoCartExists, throwNoCartExistsById } from "../utils/errors"; +import { throwCartExistsError, throwEmptyCart, throwNoCartExists, throwNoProductExists } from "../utils/errors"; +import { findProduct } from "./product.service"; -const createCart = async (userId: string, isGetOrCreate = false): Promise => { +export const createCart = async (userId: string, isGetOrCreate = false): Promise => { const userCart = await findUserCart(userId); if (userCart && isGetOrCreate) { return getCartResponse(userCart); @@ -19,22 +20,15 @@ const createCart = async (userId: string, isGetOrCreate = false): Promise => { - // return new Promise(async (res, rej) => { - const cartIdx = await findUserCartIdx(userId); - if (cartIdx === -1) { - throwNoCartExists(userId); - } - await updateCartProperty(cartIdx, CartEditableProperties.IsDeleted, true); - const response = getDeleteCartResponse(); - return response; - - // }) -} - export const deleteCart = async (userId: string): Promise => { - return deleteCart2(userId); -}; + const cartIdx = await findUserCartIdx(userId); + if (cartIdx === -1) { + throwNoCartExists(userId); + } + await updateCartProperty(cartIdx, CartEditableProperties.IsDeleted, true); + const response = getDeleteCartResponse(); + return response; +} export const postCart = async (userId: string): Promise => { return createCart(userId); @@ -44,13 +38,28 @@ export const getCart = async (userId: string): Promise => { return createCart(userId, true); } -export const updateCart = async (userId: string, updatedCart: OmitCart): Promise => { - const cartIdx = await findUserCartIdxByID(userId, updatedCart.id); +export const updateCart = async (userId: string, {productId, count}: ICartItemByID): Promise => { + const cartIdx = await findUserCartIdx(userId); if (cartIdx === -1) { - throwNoCartExistsById(userId, updatedCart.id); + throwEmptyCart(userId); + } + const productIdx = await getCartProductIdx(cartIdx, productId); + if(count>0 && productIdx===-1){ + const product = await findProduct(productId); + if(!product){ + throwNoProductExists(); + } + else{ + await pushCartItem(cartIdx, {product, count}) + } } - await updateCartProperty(cartIdx, CartEditableProperties.Items, updatedCart.items); - return await getCartByIdx(cartIdx); + 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)); } export const createOrder = async (userId: string, orderInfo: IOrderInfo) => { @@ -59,6 +68,9 @@ export const createOrder = async (userId: string, orderInfo: IOrderInfo) => { throwEmptyCart(userId); } const cart = await getCartByIdx(cartIdx); + if(cart.items.length === 0){ + throwEmptyCart(userId); + } const newOrder = generateOrder(cart, orderInfo); await pushOrder(newOrder); await updateCartProperty(cartIdx, CartEditableProperties.IsDeleted, true); diff --git a/src/service/product.service.ts b/src/service/product.service.ts index 25f3ccf..4eb0954 100644 --- a/src/service/product.service.ts +++ b/src/service/product.service.ts @@ -1,3 +1,4 @@ +import { productDB } from ".."; import { IProduct } from "../scheme/ProductScheme"; export const getTotalPrice = (products: IProduct[]):number =>{ @@ -7,3 +8,9 @@ export const getTotalPrice = (products: IProduct[]):number =>{ }); return result; } + + +export const findProduct = async (id: string):Promise=>{ + const product = await productDB.find((product) => product.id === id); + return product; +} diff --git a/src/utils/errors.ts b/src/utils/errors.ts index c2994f6..3081e66 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -37,6 +37,10 @@ export const getNoCartExists = (id: string):ResponseError => { return new ResponseError(`No cart for user ${id}`, 400); } +export const throwNoProductExists = () => { + throw new ResponseError(`No product for user`, 400); +} + export const throwEmptyCart = (id: string) => { throw new Error(`Cart for user ${id} is empty`); } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index d79fc37..d2b174a 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -16,6 +16,8 @@ export const promiseHandler = async (prom: Promise, next:NextFunction)=>{ try { return await prom; } catch (error) { + console.log('HERE') + console.log(error) next(error); } } From a259b0db61c0a4e72054a9a700888d8cd40f8d26 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 6 Oct 2023 22:44:17 +0400 Subject: [PATCH 03/17] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7bf1c65..da92012 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ # node-mentoring-shop +https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/express-layered-architecture/task We are going to create an Express application for online shop which sells different types of products (like e.g Amazon). From cbc9e2216f0332780ebf534659c86d82a9dbaa00 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 12:02:10 +0600 Subject: [PATCH 04/17] Add products routes and format all --- .vscode/settings.json | 6 +++++ src/controller/cart.controller.ts | 38 ++++++++++++++-------------- src/controller/product.controller.ts | 28 ++++++++++++++++++++ src/index.ts | 28 ++++++++++---------- src/repository/cart.repository.ts | 26 +++++++++---------- src/repository/order.repository.ts | 6 ++--- src/repository/product.repository.ts | 11 ++++++++ src/repository/user.repository.ts | 2 +- src/scheme/CartScheme.ts | 26 +++++++++---------- src/scheme/OrderScheme.ts | 6 ++--- src/scheme/ProductScheme.ts | 14 ++++++++-- src/scheme/ServiceUtils.ts | 12 ++++----- src/scheme/UserScheme.ts | 2 +- src/service/cart.service.ts | 25 +++++++++--------- src/service/product.service.ts | 31 +++++++++++++---------- src/utils/errors.ts | 28 ++++++++++---------- src/utils/productUtils.ts | 17 +++++++++++++ src/utils/utils.ts | 26 +++++++++---------- 18 files changed, 205 insertions(+), 127 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/controller/product.controller.ts create mode 100644 src/repository/product.repository.ts create mode 100644 src/utils/productUtils.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f6ef976 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "editor.formatOnSave": true, + "editor.fontSize": 15, + "workbench.colorTheme": "Default Dark Modern", + "files.autoSave": "afterDelay" +} diff --git a/src/controller/cart.controller.ts b/src/controller/cart.controller.ts index d225923..704fe76 100644 --- a/src/controller/cart.controller.ts +++ b/src/controller/cart.controller.ts @@ -1,8 +1,8 @@ import express, { NextFunction, Request, Response } from "express"; -import { createCart, createOrder, deleteCart, updateCart } from "../service/cart.service"; -import { promiseHandler } from "../utils/utils"; +import { createOrGetCart, createOrder, deleteCart, updateCart } from "../service/cart.service"; +// import { promiseHandler } from "../utils/utils"; -const cartRouter = express.Router(); +const cartRouter = express.Router(); cartRouter.use(express.json()); // cartRouter.post("/", async (req:Request, res:Response, next:NextFunction) => { @@ -11,49 +11,49 @@ cartRouter.use(express.json()); // res.status(201).send(cart); // }); -cartRouter.get("/", async (req:Request, res:Response, next:NextFunction) => { - try{ +cartRouter.get("/", async (req: Request, res: Response, next: NextFunction) => { + try { const userId = req.header('x-user-id')!; - const cart = await createCart(userId, true); + const cart = await createOrGetCart(userId, true); res.send(cart); } - catch(err){ + catch (err) { next(err); } }); -cartRouter.delete("/", async (req:Request, res:Response, next:NextFunction) => { - try{ +cartRouter.delete("/", async (req: Request, res: Response, next: NextFunction) => { + try { const userId = req.header('x-user-id')!; const data = await deleteCart(userId); res.send(data); } - catch(err){ + catch (err) { next(err); - } + } }); -cartRouter.post("/checkout", async (req:Request, res:Response, next:NextFunction) => { - try{ +cartRouter.post("/checkout", async (req: Request, res: Response, next: NextFunction) => { + try { const userId = req.header('x-user-id')!; const order = await createOrder(userId, req.body); res.send(order); } - catch(err){ + catch (err) { next(err); - } + } }); -cartRouter.put("/", async (req:Request, res:Response, next:NextFunction) => { - try{ +cartRouter.put("/", async (req: Request, res: Response, next: NextFunction) => { + try { const userId = req.header('x-user-id')!; const order = await updateCart(userId, req.body); res.send(order); } - catch(err){ + catch (err) { next(err); - } + } }); export default cartRouter; diff --git a/src/controller/product.controller.ts b/src/controller/product.controller.ts new file mode 100644 index 0000000..84c8c13 --- /dev/null +++ b/src/controller/product.controller.ts @@ -0,0 +1,28 @@ +import express, { NextFunction, Request, Response } from "express"; +import { getProduct, getProducts } from "../service/product.service"; + +const productRouter = express.Router(); +productRouter.use(express.json()); + +productRouter.get("/", async (req: Request, res: Response, next: NextFunction) => { + try { + const products = await getProducts(); + res.send(products); + } + catch (err) { + next(err); + } +}); + +productRouter.get("/:id", async (req: Request, res: Response, next: NextFunction) => { + try { + const productId = req.params.id; + const products = await getProduct(productId); + res.send(products); + } + catch (err) { + next(err); + } +}); + +export default productRouter; diff --git a/src/index.ts b/src/index.ts index 53a1e74..71d0a90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,11 @@ -import {users} from "./db/user.db.js" -import {products} from "./db/product.db.js" -import {carts} from "./db/cart.db.js" -import {orders} from "./db/order.db.js" +import { users } from "./db/user.db.js" +import { products } from "./db/product.db.js" +import { carts } from "./db/cart.db.js" +import { orders } from "./db/order.db.js" 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"; @@ -16,31 +17,32 @@ export const orderDB = orders; export const app = express(); const PORT = 3000; -const headerHandler = async (req:Request, _: Response, next: NextFunction) => { - const userId = req.header('x-user-id') ?? ''; - if(!userId){ +const headerHandler = async (req: Request, _: Response, next: NextFunction) => { + const userId = req.header('x-user-id') ?? ''; + if (!userId) { next(getForbidenError()); } const user = await findUser(userId); - if(!user){ + if (!user) { next(getUnauthorizedError()); } - next(); + next(); } -const errorHandler = async (err:Error|ResponseError, _req:Request, res: Response, _next: NextFunction) => { - if(err instanceof ResponseError){ +const errorHandler = async (err: Error | ResponseError, _req: Request, res: Response, _next: NextFunction) => { + if (err instanceof ResponseError) { res.status(err.status); } - else{ + else { res.status(500); } res.send(await getResponseError(err.message)); } app.use('/api/profile/cart', headerHandler, cartRouter, errorHandler); +app.use('/api/products', headerHandler, productRouter, errorHandler); app.listen(PORT, () => { - console.log(`Server is Fire at http://localhost:${PORT}`); + console.log(`Server is Fire at http://localhost:${PORT}`); }); diff --git a/src/repository/cart.repository.ts b/src/repository/cart.repository.ts index 97518d1..8406637 100644 --- a/src/repository/cart.repository.ts +++ b/src/repository/cart.repository.ts @@ -1,47 +1,47 @@ import { cartDB } from ".."; import { CartEditableProperties, ICart, ICartItem } from "../scheme/CartScheme"; -export const pushCart = async (cart:ICart)=>{ +export const pushCart = async (cart: ICart) => { await cartDB.push(cart); } -export const findUserCart = async (userId: string):Promise=>{ +export const findUserCart = async (userId: string): Promise => { const cart = await cartDB.find((cart) => cart.userId === userId && !cart.isDeleted); return cart; } -export const findUserCartIdx = async (userId: string)=>{ +export const findUserCartIdx = async (userId: string) => { const index = await cartDB.findIndex((cart) => cart.userId === userId && !cart.isDeleted); return index; } -export const findUserCartIdxByID = async(userId: string, cartId:string)=>{ +export const findUserCartIdxByID = async (userId: string, cartId: string) => { const index = await cartDB.findIndex((cart) => cart.userId === userId && cart.id === cartId && !cart.isDeleted); return index; } -export const updateCartProperty = async(cartIdx:number, property:CartEditableProperties, value:boolean|ICartItem[])=>{ +export const updateCartProperty = async (cartIdx: number, property: CartEditableProperties, value: boolean | ICartItem[]) => { cartDB[cartIdx][property] = value; } -export const getCartByIdx =async (cartIdx:number):Promise => { +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); +export const getCartProductIdx = async (cartIdx: number, productId: string) => { + const index = await cartDB[cartIdx].items.findIndex((item: ICartItem) => item.product.id === productId); return index; } -export const pushCartItem =async (cartIdx:number, product:ICartItem) => { +export const pushCartItem = async (cartIdx: number, product: ICartItem) => { await cartDB[cartIdx].items.push(product) } -export const updateCartItemCount =async (cartIdx:number, productIdx:number, count:number) => { - const cartItem =cartDB[cartIdx].items[productIdx]; +export const updateCartItemCount = async (cartIdx: number, productIdx: number, count: number) => { + const cartItem = cartDB[cartIdx].items[productIdx]; cartItem.count = count; } -export const deleteCartItem =async (cartIdx:number, productIdx:number) => { - const cartItems =cartDB[cartIdx].items; +export const deleteCartItem = async (cartIdx: number, productIdx: number) => { + const cartItems = cartDB[cartIdx].items; await cartItems.splice(productIdx, 1); } diff --git a/src/repository/order.repository.ts b/src/repository/order.repository.ts index 11190a6..2593aa0 100644 --- a/src/repository/order.repository.ts +++ b/src/repository/order.repository.ts @@ -1,11 +1,11 @@ import { orderDB } from ".."; import { IOrder } from "../scheme/OrderScheme"; -export const pushOrder = async (order:IOrder):Promise =>{ +export const pushOrder = async (order: IOrder): Promise => { await orderDB.push(order); - return orderDB.length-1; + 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/repository/product.repository.ts b/src/repository/product.repository.ts new file mode 100644 index 0000000..901a0f0 --- /dev/null +++ b/src/repository/product.repository.ts @@ -0,0 +1,11 @@ +import { productDB } from ".."; +import { IProduct } from "../scheme/ProductScheme"; + +export const findProduct = async (id: string): Promise => { + const product = await productDB.find((product) => product.id === id); + return product; +} + +export const findProducts = async (): Promise => { + return productDB; +} diff --git a/src/repository/user.repository.ts b/src/repository/user.repository.ts index f379efe..9dba456 100644 --- a/src/repository/user.repository.ts +++ b/src/repository/user.repository.ts @@ -1,5 +1,5 @@ import { userDB } from ".."; -export const findUser = async (userId:string)=>{ +export const findUser = async (userId: string) => { return await userDB.find((id) => id === userId); } diff --git a/src/scheme/CartScheme.ts b/src/scheme/CartScheme.ts index baf8d37..77035bf 100644 --- a/src/scheme/CartScheme.ts +++ b/src/scheme/CartScheme.ts @@ -1,39 +1,39 @@ import { IProduct } from "./ProductScheme"; import { IResponse } from "./ServiceUtils"; -export interface ICart{ - id: string; // uuid +export interface ICart { + id: string; // uuid userId: string; isDeleted: boolean; items: ICartItem[]; } -export interface ICartItem{ - product: IProduct, +export interface ICartItem { + product: IProduct, count: number, } -export interface ICartItemByID{ - productId: string, +export interface ICartItemByID { + productId: string, count: number, } -export type OmitCart = Omit; +export type OmitCart = Omit; export enum CartEditableProperties { Items = 'items', IsDeleted = "isDeleted" } -export interface ICartResponse extends IResponse{ - data: { +export interface ICartResponse extends IResponse { + data: { cart: OmitCart, totalPrice: number, - }, + }, } -export interface IDeleteCartResponse extends IResponse{ - data: { +export interface IDeleteCartResponse extends IResponse { + data: { success: boolean - }, + }, } diff --git a/src/scheme/OrderScheme.ts b/src/scheme/OrderScheme.ts index 8a9f2bb..e07b3bc 100644 --- a/src/scheme/OrderScheme.ts +++ b/src/scheme/OrderScheme.ts @@ -2,8 +2,8 @@ import { ICartItem } from "./CartScheme"; type ORDER_STATUS = 'created' | 'completed'; -export interface IOrder{ - id: string, +export interface IOrder { + id: string, userId: string; cartId: string; items: ICartItem[] @@ -22,7 +22,7 @@ export interface IOrder{ } -export type IOrderInfo = Pick; +export type IOrderInfo = Pick; export enum OrderStatuses { Created = 'created', diff --git a/src/scheme/ProductScheme.ts b/src/scheme/ProductScheme.ts index 78cc332..d834db1 100644 --- a/src/scheme/ProductScheme.ts +++ b/src/scheme/ProductScheme.ts @@ -1,6 +1,16 @@ -export interface IProduct{ - id: string; +import { IResponse } from "./ServiceUtils"; + +export interface IProduct { + id: string; title: string; description: string; price: number; } + +export interface IProductResponse extends IResponse { + data: IProduct, +} + +export interface IProductsResponse extends IResponse { + data: IProduct[], +} diff --git a/src/scheme/ServiceUtils.ts b/src/scheme/ServiceUtils.ts index f2e6b8d..5eef1bd 100644 --- a/src/scheme/ServiceUtils.ts +++ b/src/scheme/ServiceUtils.ts @@ -1,12 +1,12 @@ -export interface IResponseError{ - data:null, +export interface IResponseError { + data: null, error: { - message: string - }, + message: string + }, } -export interface IResponse{ +export interface IResponse { // eslint-disable-next-line @typescript-eslint/no-explicit-any - data:any, + data: any, error: null, } diff --git a/src/scheme/UserScheme.ts b/src/scheme/UserScheme.ts index ca7aa5e..0fe48b5 100644 --- a/src/scheme/UserScheme.ts +++ b/src/scheme/UserScheme.ts @@ -1,3 +1,3 @@ -export interface IUser{ +export interface IUser { id: string, } diff --git a/src/service/cart.service.ts b/src/service/cart.service.ts index 05c0c91..0beec0e 100644 --- a/src/service/cart.service.ts +++ b/src/service/cart.service.ts @@ -1,13 +1,14 @@ import { deleteCartItem, findUserCart, findUserCartIdx, getCartByIdx, getCartProductIdx, pushCart, 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 { IOrderInfo } from "../scheme/OrderScheme"; import { generateCart, generateOrder, getCartResponse, getDeleteCartResponse } from "../utils/cartUtils"; import { throwCartExistsError, throwEmptyCart, throwNoCartExists, throwNoProductExists } from "../utils/errors"; -import { findProduct } from "./product.service"; -export const createCart = async (userId: string, isGetOrCreate = false): Promise => { + +export const createOrGetCart = async (userId: string, isGetOrCreate = false): Promise => { const userCart = await findUserCart(userId); if (userCart && isGetOrCreate) { return getCartResponse(userCart); @@ -31,32 +32,32 @@ export const deleteCart = async (userId: string): Promise = } export const postCart = async (userId: string): Promise => { - return createCart(userId); + return createOrGetCart(userId); }; export const getCart = async (userId: string): Promise => { - return createCart(userId, true); + return createOrGetCart(userId, true); } -export const updateCart = async (userId: string, {productId, count}: ICartItemByID): Promise => { +export const updateCart = async (userId: string, { productId, count }: ICartItemByID): Promise => { const cartIdx = await findUserCartIdx(userId); if (cartIdx === -1) { throwEmptyCart(userId); } const productIdx = await getCartProductIdx(cartIdx, productId); - if(count>0 && productIdx===-1){ + if (count > 0 && productIdx === -1) { const product = await findProduct(productId); - if(!product){ + if (!product) { throwNoProductExists(); } - else{ - await pushCartItem(cartIdx, {product, count}) + else { + await pushCartItem(cartIdx, { product, count }) } } - if (count>0 && productIdx>=0) { + if (count > 0 && productIdx >= 0) { await updateCartItemCount(cartIdx, productIdx, count); } - if (count===0 && productIdx>=0) { + if (count === 0 && productIdx >= 0) { await deleteCartItem(cartIdx, productIdx); } return await getCartResponse(await getCartByIdx(cartIdx)); @@ -68,7 +69,7 @@ export const createOrder = async (userId: string, orderInfo: IOrderInfo) => { throwEmptyCart(userId); } const cart = await getCartByIdx(cartIdx); - if(cart.items.length === 0){ + if (cart.items.length === 0) { throwEmptyCart(userId); } const newOrder = generateOrder(cart, orderInfo); diff --git a/src/service/product.service.ts b/src/service/product.service.ts index 4eb0954..3b7708a 100644 --- a/src/service/product.service.ts +++ b/src/service/product.service.ts @@ -1,16 +1,19 @@ -import { productDB } from ".."; -import { IProduct } from "../scheme/ProductScheme"; +import { findProduct, findProducts } from "../repository/product.repository"; +import { IProductResponse, IProductsResponse } from "../scheme/ProductScheme"; +import { throwNoProductExists } from "../utils/errors"; +import { getProductResponse, getProductsResponse } from "../utils/productUtils"; -export const getTotalPrice = (products: IProduct[]):number =>{ - let result = 0; - products.map(product=>{ - result=+product.price; - }); - return result; -} +export const getProduct = async (productId: string): Promise => { + const product = await findProduct(productId); + if (!product) { + throwNoProductExists(); + } + else { + return getProductResponse(product); + } +}; - -export const findProduct = async (id: string):Promise=>{ - const product = await productDB.find((product) => product.id === id); - return product; -} +export const getProducts = async (): Promise => { + const products = await findProducts(); + return getProductsResponse(products); +}; diff --git a/src/utils/errors.ts b/src/utils/errors.ts index 3081e66..f70c90e 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -1,19 +1,19 @@ export class ResponseError extends Error implements IResponseError { status: number; - constructor(message:string, status:number) { - super(message); - this.status = status; - } + constructor(message: string, status: number) { + super(message); + this.status = status; + } } - + interface IResponseError extends Error { - status: number; + status: number; } - -const getResposeError = (message:string, status: number):ResponseError=>{ + +const getResposeError = (message: string, status: number): ResponseError => { const err = new ResponseError(message, status); - return err; + return err; } @@ -21,7 +21,7 @@ export const throwNoUser = (id: string) => { throw new Error(`No user ${id}`); } -export const throwCartExistsError = (id: string):ResponseError => { +export const throwCartExistsError = (id: string): ResponseError => { throw new ResponseError(`Cart for user ${id} already exists`, 400); } @@ -33,7 +33,7 @@ export const throwNoCartExists = (id: string) => { throw new ResponseError(`No cart for user ${id}`, 400); } -export const getNoCartExists = (id: string):ResponseError => { +export const getNoCartExists = (id: string): ResponseError => { return new ResponseError(`No cart for user ${id}`, 400); } @@ -45,14 +45,14 @@ export const throwEmptyCart = (id: string) => { throw new Error(`Cart for user ${id} is empty`); } -export const throwUnauthorized = (status=401) => { +export const throwUnauthorized = (status = 401) => { throw getResposeError(`Header x-user-id is missing or no user with such id`, status) } -export const getUnauthorizedError = (status=401) => { +export const getUnauthorizedError = (status = 401) => { return getResposeError(`Header x-user-id is missing or no user with such id`, status) } -export const getForbidenError = (status=403) => { +export const getForbidenError = (status = 403) => { return getResposeError(`You must be authorized user`, status) } diff --git a/src/utils/productUtils.ts b/src/utils/productUtils.ts new file mode 100644 index 0000000..efc8800 --- /dev/null +++ b/src/utils/productUtils.ts @@ -0,0 +1,17 @@ +import { IProduct, IProductResponse, IProductsResponse } from "../scheme/ProductScheme"; + +export const getProductResponse = (product: IProduct): IProductResponse => { + const response: IProductResponse = { + data: product, + error: null, + } + return response; +} + +export const getProductsResponse = (products: IProduct[]): IProductsResponse => { + const response: IProductsResponse = { + data: products, + error: null, + } + return response; +} diff --git a/src/utils/utils.ts b/src/utils/utils.ts index d2b174a..3cc6b4c 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -3,37 +3,37 @@ import cloneDeep from 'lodash.clonedeep'; import { NextFunction } from 'express'; import { IResponse, IResponseError } from '../scheme/ServiceUtils'; -export const getUUID = ()=>{ +export const getUUID = () => { return uuid(); }; -export const createDeepCopy = (arr: T[]): T[] =>{ +export const createDeepCopy = (arr: T[]): T[] => { return cloneDeep(arr); } -export const promiseHandler = async (prom: Promise, next:NextFunction)=>{ +export const promiseHandler = async (prom: Promise, next: NextFunction) => { try { - return await prom; - } catch (error) { + return await prom; + } catch (error) { console.log('HERE') console.log(error) - next(error); - } + next(error); + } } -export const getResponseError = async (message:string):Promise=>{ +export const getResponseError = async (message: string): Promise => { return { - data:null, - error:{ + data: null, + error: { message: message } } } -export const getResponse = ():IResponse=>{ +export const getResponse = (): IResponse => { return { - data:"", - error:null + data: "", + error: null } } From 8fdeb1d135404d29da95c74ab09f8ca1d18a7a7d Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 12:19:13 +0600 Subject: [PATCH 05/17] Remove consoles for debug --- src/utils/utils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 3cc6b4c..7d7e611 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -16,8 +16,6 @@ export const promiseHandler = async (prom: Promise, next: NextFunction) => try { return await prom; } catch (error) { - console.log('HERE') - console.log(error) next(error); } } From 875b9e127c3cbabd4767779e034dc3c0fb22db40 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 12:38:17 +0600 Subject: [PATCH 06/17] Add joi validation for cart put --- package-lock.json | 44 +++++++++++++++++++++++++++++++ package.json | 1 + src/controller/cart.controller.ts | 20 +++++++++++++- src/utils/errors.ts | 4 +++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 43ced24..9de0178 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "express": "^4.18.2", + "joi": "^17.11.0", "lodash.clonedeep": "^4.5.0", "uuid": "^9.0.1" }, @@ -126,6 +127,19 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.11", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", @@ -242,6 +256,24 @@ "node": ">= 8" } }, + "node_modules/@sideway/address": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", + "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==" + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1822,6 +1854,18 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/joi": { + "version": "17.11.0", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.11.0.tgz", + "integrity": "sha512-NgB+lZLNoqISVy1rZocE9PZI36bL/77ie924Ri43yEvi9GUUMPeyVIr8KdFTMUlby1p0PBYMk9spIxEUQYqrJQ==", + "dependencies": { + "@hapi/hoek": "^9.0.0", + "@hapi/topo": "^5.0.0", + "@sideway/address": "^4.1.3", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", diff --git a/package.json b/package.json index a2b994f..59441cb 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "express": "^4.18.2", + "joi": "^17.11.0", "lodash.clonedeep": "^4.5.0", "uuid": "^9.0.1" } diff --git a/src/controller/cart.controller.ts b/src/controller/cart.controller.ts index 704fe76..13ab135 100644 --- a/src/controller/cart.controller.ts +++ b/src/controller/cart.controller.ts @@ -1,10 +1,28 @@ 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()); +const putSchema = Joi.object({ + productId: Joi.string().uuid().required(), + count: Joi.number().integer().min(0).max(20).required() +}) + +const putValidator = async (req: Request, _: Response, next: NextFunction) => { + try { + await putSchema.validateAsync(req.body); + next(); + } + 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); @@ -45,7 +63,7 @@ cartRouter.post("/checkout", async (req: Request, res: Response, next: NextFunct } }); -cartRouter.put("/", async (req: Request, res: Response, next: NextFunction) => { +cartRouter.put("/", putValidator, async (req: Request, res: Response, next: NextFunction) => { try { const userId = req.header('x-user-id')!; const order = await updateCart(userId, req.body); diff --git a/src/utils/errors.ts b/src/utils/errors.ts index f70c90e..620d5d4 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -56,3 +56,7 @@ export const getUnauthorizedError = (status = 401) => { export const getForbidenError = (status = 403) => { return getResposeError(`You must be authorized user`, status) } + +export const getPutSchemeError = (status = 400) => { + return getResposeError(`Bad request body`, status) +} From 9205f158ce3f9bfbb12df37839fbdd197d50a60c Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 11:13:53 +0400 Subject: [PATCH 07/17] Update README.md --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index da92012..a48d004 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,35 @@ https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/express-layered-architecture/task We are going to create an Express application for online shop which sells different types of products (like e.g Amazon). + +The application has 4 primary entities: + +User - can add some products to the cart and then order them ([scheme](https://github.com/kandalova/node-mentoring-shop/blob/task_6_shop_express.js/src/scheme/UserScheme.ts)). + +Product - represents product information that user can order ([scheme](https://github.com/kandalova/node-mentoring-shop/blob/task_6_shop_express.js/src/scheme/ProductScheme.ts)). + +Cart - contains a list of products and their amount that user wants to order ([scheme](https://github.com/kandalova/node-mentoring-shop/blob/task_6_shop_express.js/src/scheme/CartScheme.ts)). + +Order - contains list of products from cart that user has ordered ([scheme](https://github.com/kandalova/node-mentoring-shop/blob/task_6_shop_express.js/src/scheme/OrderScheme.ts)). + +**Relations between entities:** + +Each User can have only one non-deleted Cart at a time. +Each Cart is attached to a specific User. + +One User can have multiple Order. Each Order is attached to a specific User. + +Cart contains a list of products that user wants to order with the amount of those products specified. + +**Implementation criteria:** + +- TypeScript is used. +- Application is implemented following Three Layered Architecture. Layers are separated by file names. For example xxx.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. + +**API endpoints** +image From cb16f92b14626f7d8321a2981f8c8214d58e0a39 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 13:20:13 +0600 Subject: [PATCH 08/17] Add swagger example --- src/swagger.yaml | 693 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 693 insertions(+) create mode 100644 src/swagger.yaml diff --git a/src/swagger.yaml b/src/swagger.yaml new file mode 100644 index 0000000..ad538e7 --- /dev/null +++ b/src/swagger.yaml @@ -0,0 +1,693 @@ +openapi: 3.0.3 +info: + title: Node.js GMP Express application + version: 1.0.0 +tags: + - name: cart + - name: product + - name: auth +paths: + /api/profile/cart: + get: + tags: + - cart + summary: Get user cart or create if it is missing + parameters: + - in: header + name: x-user-id + schema: + type: string + format: uuid + required: true + description: User id (uuid) + example: 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + responses: + '200': + description: Returns user cart + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: + cart: + id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + items: + - product: + id: 891389f0-4312-42d6-a650-6fda0959c734 + title: Book + description: Interesting book + price: 200 + count: 2 + total: 400 + error: null + '403': + description: Forbidden (when authorization header is missing) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: You must be authorized user + '401': + description: Unauthorized (when no user matching authorization header is found) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: User is not authorized + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: 'Internal Server error' + put: + tags: + - cart + summary: Update user cart + parameters: + - in: header + name: x-user-id + schema: + type: string + format: uuid + required: true + description: User id (uuid) + example: 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateCartRequestBody' + responses: + '200': + description: Cart can be updated in the following ways - 1) products can be added 2) products can be dropped 3) amount of product might have changed. Request body to be provided contains a snapshot of cart after changes. + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: + cart: + id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + items: + - product: + id: 891389f0-4312-42d6-a650-6fda0959c734 + title: Book + description: Interesting book + price: 200 + count: 2 + total: 400 + error: null + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: Products are not valid + '403': + description: Forbidden (when authorization header is missing) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: You must be authorized user + '401': + description: Unauthorized (when no user matching authorization header is found) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: User is not authorized + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: Cart was not found + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: 'Internal Server error' + delete: + tags: + - cart + summary: Empty user cart + parameters: + - in: header + name: x-user-id + schema: + type: string + format: uuid + required: true + description: User id (uuid) + example: 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + responses: + '200': + description: Returns success = true if cart was successfully emptied + content: + application/json: + schema: + $ref: '#/components/schemas/EmptySuccessResponse' + example: + data: + success: true + error: null + '403': + description: Forbidden (when authorization header is missing) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: You must be authorized user + '401': + description: Unauthorized (when no user matching authorization header is found) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: User is not authorized + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: 'Internal Server error' + /api/profile/cart/checkout: + post: + tags: + - cart + summary: Create an order + parameters: + - in: header + name: x-user-id + schema: + type: string + format: uuid + required: true + description: User id (uuid) + example: 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/CheckoutResponse' + example: + data: + order: + id: 6c36d6fa-f694-4f9c-9b2c-6f7049d38f4a + userId: 6dc52b3c-de7e-431a-84b8-0ec56e0774d4 + cartId: cadff0c5-0079-4db8-b6bf-84c9c2633ca3 + items: + - product: + id: 5c293ad0-19d0-41ee-baa3-4c648f9f7697 + title: Book + description: Interesting book + price: 200 + count: 2 + - product: + id: afdd68c4-d359-45e6-b9fd-c8fdb2a162a0 + title: Pen + description: Cute pen + price: 20 + count: 5 + payment: + type: paypal + address: London + creditCard: 1234-1234-1234-1234 + delivery: + type: post + address: London + comments: '' + status: created + total: 500 + error: null + '403': + description: Forbidden (when authorization header is missing) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: You must be authorized user + '401': + description: Unauthorized (when no user matching authorization header is found) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: User is not authorized + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: Cart is empty + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: 'Internal Server error' + /api/products: + get: + tags: + - product + summary: Returns a list of products + parameters: + - in: header + name: x-user-id + schema: + type: string + format: uuid + required: true + description: User id (uuid) + example: 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + responses: + '200': + description: Returns a list of all products available + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/ProductsResponse' + example: + data: + - id: 5c293ad0-19d0-41ee-baa3-4c648f9f7697 + title: Book + description: Interesting book + price: 200 + - id: afdd68c4-d359-45e6-b9fd-c8fdb2a162a0 + title: Pen + description: Cute pen + price: 20 + error: null + '403': + description: Forbidden (when authorization header is missing) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: You must be authorized user + '401': + description: Unauthorized (when no user matching authorization header is found) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: User is not authorized + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: 'Internal Server error' + /api/products/{productId}: + get: + tags: + - product + summary: Returns single product + parameters: + - name: productId + in: path + description: Id (uuid) of product to return + required: true + schema: + type: number + - in: header + name: x-user-id + schema: + type: string + format: uuid + required: true + description: User id (uuid) + example: 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + responses: + '200': + description: Successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ProductResponse' + example: + data: + id: 5c293ad0-19d0-41ee-baa3-4c648f9f7697 + title: Book + description: Interesting book + price: 200 + error: null + '403': + description: Forbidden (when authorization header is missing) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: You must be authorized user + '401': + description: Unauthorized (when no user matching authorization header is found) + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: User is not authorized + '404': + description: Not found + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: No product with such id + '500': + description: Internal server error + content: + application/json: + schema: + $ref: '#/components/schemas/CartResponse' + example: + data: null + error: + message: 'Internal Server error' + /api/auth/register: + post: + tags: + - auth + summary: Create new user (for Module 9) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUserRequestBody' + responses: + '200': + description: Returns new user + content: + application/json: + schema: + $ref: '#/components/schemas/CreateUserResponse' + example: + data: + id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + email: ann.jones@epam.com + password: 1234kdksla020-1444 + role: admin + error: null + '400': + description: Bad request + content: + application/json: + example: + data: null + error: + message: Email is not valid + '500': + description: Internal server error + content: + application/json: + example: + data: null + error: + message: 'Internal Server error' + /api/auth/login: + post: + tags: + - auth + summary: Login user (for Module 9) + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UserCredentials' + responses: + '200': + description: Returns new user + content: + application/json: + schema: + $ref: '#/components/schemas/LoginUserResponse' + example: + data: + token: VugRyl8SX88-LOZhvJxupjfWX=rATjop1kFw/NcshAQ4QdX0moMh9w9pVtL4rowLVoVpjpYBSKBUJsfqjnjnr-ImD/XAWJUdXIFfy3!7ViC/pxSP5zNefrgMAt2=?3=iv7Gga=zjphHqog6Jdy?AliWwYROxe4AUzQAxM!Nc=Hxax-0hJRJ!ijgE-=6RFis!cuHXxpJBA=EpoTYmla247q6rrm1RA!WLq3rJOX1itwD-LGb8hdGVXwGFgxy9QNJ/ + error: null + '404': + description: Bad request + content: + application/json: + example: + data: null + error: + message: No user with such email or password + '500': + description: Internal server error + content: + application/json: + example: + data: null + error: + message: 'Internal Server error' +components: + schemas: + Cart: + type: object + properties: + id: + type: string + description: Cart id (uuid) + example: dd5ec5ab-deaa-419c-8a6b-7e67b1f7ec87 + items: + type: array + description: Items added to cart + items: + $ref: '#/components/schemas/CartItem' + CartItem: + type: object + properties: + product: + $ref: '#/components/schemas/Product' + count: + type: number + description: Total count of specific products + example: 2 + Product: + type: object + properties: + id: + type: string + description: Product id (uuid) + example: 5c293ad0-19d0-41ee-baa3-4c648f9f7697 + title: + type: string + description: Product name + example: Book + description: + type: string + description: Product description + example: Interesting book + price: + type: number + description: Product price + example: 200 + Order: + type: object + properties: + id: + type: string + userId: + type: string + cartId: + type: string + items: + type: array + items: + $ref: '#/components/schemas/CartItem' + payment: + type: object + properties: + type: + type: string + address: + type: string + creditCard: + type: string + delivery: + type: object + properties: + type: + type: string + address: + type: string + comments: + type: string + status: + type: string + total: + type: number + CartResponse: + type: object + properties: + data: + type: object + properties: + cart: + $ref: '#/components/schemas/Cart' + total: + type: number + error: + $ref: '#/components/schemas/ErrorResponse' + CheckoutResponse: + type: object + properties: + data: + type: object + properties: + order: + $ref: '#/components/schemas/Order' + error: + $ref: '#/components/schemas/ErrorResponse' + ProductResponse: + type: object + properties: + data: + $ref: '#/components/schemas/Product' + error: + $ref: '#/components/schemas/ErrorResponse' + ProductsResponse: + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/Product' + error: + $ref: '#/components/schemas/ErrorResponse' + UpdateCartRequestBody: + type: object + properties: + productId: + type: string + example: "915b2f40-9fd9-47f2-9b51-628f3dc69aac" + count: + type: number + example: 5 + UserCredentials: + type: object + properties: + email: + type: string + example: "ann.jones@epam.com" + password: + type: string + example: "DDQldls?kdpw0fk" + CreateUserRequestBody: + allOf: + - $ref: '#/components/schemas/UserCredentials' + - type: object + properties: + role: + type: string + enum: [admin, user] + example: "admin" + CreateUserResponse: + allOf: + - $ref: '#/components/schemas/CreateUserRequestBody' + - type: object + properties: + id: + type: string + example: "fdbbb574-266b-4b2e-ad2d-790bcf4765c2" + LoginUserResponse: + type: object + properties: + token: + type: string + example: "VugRyl8SX88-LOZhvJxupjfWX=rATjop1kFw/NcshAQ4QdX0moMh9w9pVtL4rowLVoVpjpYBSKBUJsfqjnjnr-ImD/XAWJUdXIFfy3!7ViC/pxSP5zNefrgMAt2=?3=iv7Gga=zjphHqog6Jdy?AliWwYROxe4AUzQAxM!Nc=Hxax-0hJRJ!ijgE-=6RFis!cuHXxpJBA=EpoTYmla247q6rrm1RA!WLq3rJOX1itwD-LGb8hdGVXwGFgxy9QNJ/" + EmptySuccessResponse: + type: object + properties: + data: + type: object + properties: + success: + type: boolean + error: + $ref: '#/components/schemas/ErrorResponse' + ErrorResponse: + type: object + nullable: true + properties: + message: + type: string From bdbb2a68b75af45ec33f07ccc9fc7fa8bd994106 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 13:34:36 +0600 Subject: [PATCH 09/17] Try render swagger --- swagger.md | 165 +++++++++++++++++++++++++++++++ src/swagger.yaml => swagger.yaml | 0 2 files changed, 165 insertions(+) create mode 100644 swagger.md rename src/swagger.yaml => swagger.yaml (100%) diff --git a/swagger.md b/swagger.md new file mode 100644 index 0000000..50ed2e9 --- /dev/null +++ b/swagger.md @@ -0,0 +1,165 @@ +# Node.js GMP Express application +## Version: 1.0.0 + +### /api/profile/cart + +#### GET +##### Summary: + +Get user cart or create if it is missing + +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| x-user-id | header | User id (uuid) | Yes | string (uuid) | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Returns user cart | +| 401 | Unauthorized (when no user matching authorization header is found) | +| 403 | Forbidden (when authorization header is missing) | +| 500 | Internal server error | + +#### PUT +##### Summary: + +Update user cart + +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| x-user-id | header | User id (uuid) | Yes | string (uuid) | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Cart can be updated in the following ways - 1) products can be added 2) products can be dropped 3) amount of product might have changed. Request body to be provided contains a snapshot of cart after changes. | +| 400 | Bad request | +| 401 | Unauthorized (when no user matching authorization header is found) | +| 403 | Forbidden (when authorization header is missing) | +| 404 | Not Found | +| 500 | Internal server error | + +#### DELETE +##### Summary: + +Empty user cart + +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| x-user-id | header | User id (uuid) | Yes | string (uuid) | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Returns success = true if cart was successfully emptied | +| 401 | Unauthorized (when no user matching authorization header is found) | +| 403 | Forbidden (when authorization header is missing) | +| 500 | Internal server error | + +### /api/profile/cart/checkout + +#### POST +##### Summary: + +Create an order + +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| x-user-id | header | User id (uuid) | Yes | string (uuid) | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Successful operation | +| 400 | Bad request | +| 401 | Unauthorized (when no user matching authorization header is found) | +| 403 | Forbidden (when authorization header is missing) | +| 500 | Internal server error | + +### /api/products + +#### GET +##### Summary: + +Returns a list of products + +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| x-user-id | header | User id (uuid) | Yes | string (uuid) | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Returns a list of all products available | +| 401 | Unauthorized (when no user matching authorization header is found) | +| 403 | Forbidden (when authorization header is missing) | +| 500 | Internal server error | + +### /api/products/{productId} + +#### GET +##### Summary: + +Returns single product + +##### Parameters + +| Name | Located in | Description | Required | Schema | +| ---- | ---------- | ----------- | -------- | ---- | +| productId | path | Id (uuid) of product to return | Yes | number | +| x-user-id | header | User id (uuid) | Yes | string (uuid) | + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Successful operation | +| 401 | Unauthorized (when no user matching authorization header is found) | +| 403 | Forbidden (when authorization header is missing) | +| 404 | Not found | +| 500 | Internal server error | + +### /api/auth/register + +#### POST +##### Summary: + +Create new user (for Module 9) + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Returns new user | +| 400 | Bad request | +| 500 | Internal server error | + +### /api/auth/login + +#### POST +##### Summary: + +Login user (for Module 9) + +##### Responses + +| Code | Description | +| ---- | ----------- | +| 200 | Returns new user | +| 404 | Bad request | +| 500 | Internal server error | diff --git a/src/swagger.yaml b/swagger.yaml similarity index 100% rename from src/swagger.yaml rename to swagger.yaml From d51849ccd16b97613cbe10786d58aceb17ff3dd3 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 13:44:27 +0600 Subject: [PATCH 10/17] Fix and convert swagger --- swagger.md | 1938 +++++++++++++++++++++++++++++++++++++++++++++++--- swagger.yaml | 101 --- 2 files changed, 1823 insertions(+), 216 deletions(-) diff --git a/swagger.md b/swagger.md index 50ed2e9..a5b8f11 100644 --- a/swagger.md +++ b/swagger.md @@ -1,165 +1,1873 @@ -# Node.js GMP Express application -## Version: 1.0.0 +--- +title: Node.js GMP Express application v1.0.0 +language_tabs: + - shell: Shell + - http: HTTP + - javascript: JavaScript + - ruby: Ruby + - python: Python + - php: PHP + - java: Java + - go: Go +toc_footers: [] +includes: [] +search: true +highlight_theme: darkula +headingLevel: 2 -### /api/profile/cart +--- -#### GET -##### Summary: + -Get user cart or create if it is missing +

Node.js GMP Express application v1.0.0

-##### Parameters +> Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu. -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | ----------- | -------- | ---- | -| x-user-id | header | User id (uuid) | Yes | string (uuid) | +

cart

-##### Responses +## get__api_profile_cart -| Code | Description | -| ---- | ----------- | -| 200 | Returns user cart | -| 401 | Unauthorized (when no user matching authorization header is found) | -| 403 | Forbidden (when authorization header is missing) | -| 500 | Internal server error | +> Code samples -#### PUT -##### Summary: +```shell +# You can also use wget +curl -X GET /api/profile/cart \ + -H 'Accept: application/json' \ + -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -Update user cart +``` -##### Parameters +```http +GET /api/profile/cart HTTP/1.1 -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | ----------- | -------- | ---- | -| x-user-id | header | User id (uuid) | Yes | string (uuid) | +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c -##### Responses +``` -| Code | Description | -| ---- | ----------- | -| 200 | Cart can be updated in the following ways - 1) products can be added 2) products can be dropped 3) amount of product might have changed. Request body to be provided contains a snapshot of cart after changes. | -| 400 | Bad request | -| 401 | Unauthorized (when no user matching authorization header is found) | -| 403 | Forbidden (when authorization header is missing) | -| 404 | Not Found | -| 500 | Internal server error | +```javascript -#### DELETE -##### Summary: +const headers = { + 'Accept':'application/json', + 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +}; -Empty user cart +fetch('/api/profile/cart', +{ + method: 'GET', -##### Parameters + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | ----------- | -------- | ---- | -| x-user-id | header | User id (uuid) | Yes | string (uuid) | +``` -##### Responses +```ruby +require 'rest-client' +require 'json' -| Code | Description | -| ---- | ----------- | -| 200 | Returns success = true if cart was successfully emptied | -| 401 | Unauthorized (when no user matching authorization header is found) | -| 403 | Forbidden (when authorization header is missing) | -| 500 | Internal server error | +headers = { + 'Accept' => 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} -### /api/profile/cart/checkout +result = RestClient.get '/api/profile/cart', + params: { + }, headers: headers -#### POST -##### Summary: +p JSON.parse(result) -Create an order +``` -##### Parameters +```python +import requests +headers = { + 'Accept': 'application/json', + 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | ----------- | -------- | ---- | -| x-user-id | header | User id (uuid) | Yes | string (uuid) | +r = requests.get('/api/profile/cart', headers = headers) -##### Responses +print(r.json()) -| Code | Description | -| ---- | ----------- | -| 200 | Successful operation | -| 400 | Bad request | -| 401 | Unauthorized (when no user matching authorization header is found) | -| 403 | Forbidden (when authorization header is missing) | -| 500 | Internal server error | +``` -### /api/products +```php + 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', +); -##### Parameters +$client = new \GuzzleHttp\Client(); -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | ----------- | -------- | ---- | -| x-user-id | header | User id (uuid) | Yes | string (uuid) | +// Define array of request body. +$request_body = array(); -##### Responses +try { + $response = $client->request('GET','/api/profile/cart', array( + 'headers' => $headers, + 'json' => $request_body, + ) + ); + print_r($response->getBody()->getContents()); + } + catch (\GuzzleHttp\Exception\BadResponseException $e) { + // handle exception or api errors. + print_r($e->getMessage()); + } -| Code | Description | -| ---- | ----------- | -| 200 | Returns a list of all products available | -| 401 | Unauthorized (when no user matching authorization header is found) | -| 403 | Forbidden (when authorization header is missing) | -| 500 | Internal server error | + // ... -### /api/products/{productId} +``` -#### GET -##### Summary: +```java +URL obj = new URL("/api/profile/cart"); +HttpURLConnection con = (HttpURLConnection) obj.openConnection(); +con.setRequestMethod("GET"); +int responseCode = con.getResponseCode(); +BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); +String inputLine; +StringBuffer response = new StringBuffer(); +while ((inputLine = in.readLine()) != null) { + response.append(inputLine); +} +in.close(); +System.out.println(response.toString()); -Returns single product +``` -##### Parameters +```go +package main -| Name | Located in | Description | Required | Schema | -| ---- | ---------- | ----------- | -------- | ---- | -| productId | path | Id (uuid) of product to return | Yes | number | -| x-user-id | header | User id (uuid) | Yes | string (uuid) | +import ( + "bytes" + "net/http" +) -##### Responses +func main() { -| Code | Description | -| ---- | ----------- | -| 200 | Successful operation | -| 401 | Unauthorized (when no user matching authorization header is found) | -| 403 | Forbidden (when authorization header is missing) | -| 404 | Not found | -| 500 | Internal server error | + headers := map[string][]string{ + "Accept": []string{"application/json"}, + "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, + } -### /api/auth/register + data := bytes.NewBuffer([]byte{jsonReq}) + req, err := http.NewRequest("GET", "/api/profile/cart", data) + req.Header = headers -#### POST -##### Summary: + client := &http.Client{} + resp, err := client.Do(req) + // ... +} -Create new user (for Module 9) +``` -##### Responses +`GET /api/profile/cart` -| Code | Description | -| ---- | ----------- | -| 200 | Returns new user | -| 400 | Bad request | -| 500 | Internal server error | +*Get user cart or create if it is missing* -### /api/auth/login +

Parameters

-#### POST -##### Summary: +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|x-user-id|header|string(uuid)|true|User id (uuid)| -Login user (for Module 9) +> Example responses -##### Responses +> 200 Response + +```json +{ + "data": { + "cart": { + "id": "eb5a26af-6e4c-4f31-a9b1-3450d42ac66c", + "items": [ + { + "product": { + "id": "891389f0-4312-42d6-a650-6fda0959c734", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "count": 2 + } + ] + }, + "total": 400 + }, + "error": null +} +``` + +> 401 Response + +```json +{ + "data": null, + "error": { + "message": "User is not authorized" + } +} +``` + +> 403 Response + +```json +{ + "data": null, + "error": { + "message": "You must be authorized user" + } +} +``` + +> 500 Response + +```json +{ + "data": null, + "error": { + "message": "Internal Server error" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Returns user cart|[CartResponse](#schemacartresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no user matching authorization header is found)|[CartResponse](#schemacartresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when authorization header is missing)|[CartResponse](#schemacartresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)| + + + +## put__api_profile_cart + +> Code samples + +```shell +# You can also use wget +curl -X PUT /api/profile/cart \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' \ + -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + +``` + +```http +PUT /api/profile/cart HTTP/1.1 + +Content-Type: application/json +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```javascript +const inputBody = '{ + "productId": "915b2f40-9fd9-47f2-9b51-628f3dc69aac", + "count": 5 +}'; +const headers = { + 'Content-Type':'application/json', + 'Accept':'application/json', + 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +}; + +fetch('/api/profile/cart', +{ + method: 'PUT', + body: inputBody, + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +```ruby +require 'rest-client' +require 'json' + +headers = { + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +result = RestClient.put '/api/profile/cart', + params: { + }, headers: headers + +p JSON.parse(result) + +``` + +```python +import requests +headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +r = requests.put('/api/profile/cart', headers = headers) + +print(r.json()) + +``` + +```php + 'application/json', + 'Accept' => 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', +); + +$client = new \GuzzleHttp\Client(); + +// Define array of request body. +$request_body = array(); + +try { + $response = $client->request('PUT','/api/profile/cart', array( + 'headers' => $headers, + 'json' => $request_body, + ) + ); + print_r($response->getBody()->getContents()); + } + catch (\GuzzleHttp\Exception\BadResponseException $e) { + // handle exception or api errors. + print_r($e->getMessage()); + } + + // ... + +``` + +```java +URL obj = new URL("/api/profile/cart"); +HttpURLConnection con = (HttpURLConnection) obj.openConnection(); +con.setRequestMethod("PUT"); +int responseCode = con.getResponseCode(); +BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); +String inputLine; +StringBuffer response = new StringBuffer(); +while ((inputLine = in.readLine()) != null) { + response.append(inputLine); +} +in.close(); +System.out.println(response.toString()); + +``` + +```go +package main + +import ( + "bytes" + "net/http" +) + +func main() { + + headers := map[string][]string{ + "Content-Type": []string{"application/json"}, + "Accept": []string{"application/json"}, + "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, + } + + data := bytes.NewBuffer([]byte{jsonReq}) + req, err := http.NewRequest("PUT", "/api/profile/cart", data) + req.Header = headers + + client := &http.Client{} + resp, err := client.Do(req) + // ... +} + +``` + +`PUT /api/profile/cart` + +*Update user cart* + +> Body parameter + +```json +{ + "productId": "915b2f40-9fd9-47f2-9b51-628f3dc69aac", + "count": 5 +} +``` + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|x-user-id|header|string(uuid)|true|User id (uuid)| +|body|body|[UpdateCartRequestBody](#schemaupdatecartrequestbody)|false|none| + +> Example responses + +> 200 Response + +```json +{ + "data": { + "cart": { + "id": "eb5a26af-6e4c-4f31-a9b1-3450d42ac66c", + "items": [ + { + "product": { + "id": "891389f0-4312-42d6-a650-6fda0959c734", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "count": 2 + } + ] + }, + "total": 400 + }, + "error": null +} +``` + +> 400 Response + +```json +{ + "data": null, + "error": { + "message": "Products are not valid" + } +} +``` + +> 401 Response + +```json +{ + "data": null, + "error": { + "message": "User is not authorized" + } +} +``` + +> 403 Response + +```json +{ + "data": null, + "error": { + "message": "You must be authorized user" + } +} +``` + +> 404 Response + +```json +{ + "data": null, + "error": { + "message": "Cart was not found" + } +} +``` + +> 500 Response + +```json +{ + "data": null, + "error": { + "message": "Internal Server error" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Cart can be updated in the following ways - 1) products can be added 2) products can be dropped 3) amount of product might have changed. Request body to be provided contains a snapshot of cart after changes.|[CartResponse](#schemacartresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request|[CartResponse](#schemacartresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no user matching authorization header is found)|[CartResponse](#schemacartresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when authorization header is missing)|[CartResponse](#schemacartresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not Found|[CartResponse](#schemacartresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)| + + + +## delete__api_profile_cart + +> Code samples + +```shell +# You can also use wget +curl -X DELETE /api/profile/cart \ + -H 'Accept: application/json' \ + -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + +``` + +```http +DELETE /api/profile/cart HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```javascript + +const headers = { + 'Accept':'application/json', + 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +}; + +fetch('/api/profile/cart', +{ + method: 'DELETE', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +```ruby +require 'rest-client' +require 'json' + +headers = { + 'Accept' => 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +result = RestClient.delete '/api/profile/cart', + params: { + }, headers: headers + +p JSON.parse(result) + +``` + +```python +import requests +headers = { + 'Accept': 'application/json', + 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +r = requests.delete('/api/profile/cart', headers = headers) + +print(r.json()) + +``` + +```php + 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', +); + +$client = new \GuzzleHttp\Client(); + +// Define array of request body. +$request_body = array(); + +try { + $response = $client->request('DELETE','/api/profile/cart', array( + 'headers' => $headers, + 'json' => $request_body, + ) + ); + print_r($response->getBody()->getContents()); + } + catch (\GuzzleHttp\Exception\BadResponseException $e) { + // handle exception or api errors. + print_r($e->getMessage()); + } + + // ... + +``` + +```java +URL obj = new URL("/api/profile/cart"); +HttpURLConnection con = (HttpURLConnection) obj.openConnection(); +con.setRequestMethod("DELETE"); +int responseCode = con.getResponseCode(); +BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); +String inputLine; +StringBuffer response = new StringBuffer(); +while ((inputLine = in.readLine()) != null) { + response.append(inputLine); +} +in.close(); +System.out.println(response.toString()); + +``` + +```go +package main + +import ( + "bytes" + "net/http" +) + +func main() { + + headers := map[string][]string{ + "Accept": []string{"application/json"}, + "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, + } + + data := bytes.NewBuffer([]byte{jsonReq}) + req, err := http.NewRequest("DELETE", "/api/profile/cart", data) + req.Header = headers + + client := &http.Client{} + resp, err := client.Do(req) + // ... +} + +``` + +`DELETE /api/profile/cart` + +*Empty user cart* + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|x-user-id|header|string(uuid)|true|User id (uuid)| + +> Example responses + +> 200 Response + +```json +{ + "data": { + "success": true + }, + "error": null +} +``` + +> 401 Response + +```json +{ + "data": null, + "error": { + "message": "User is not authorized" + } +} +``` + +> 403 Response + +```json +{ + "data": null, + "error": { + "message": "You must be authorized user" + } +} +``` + +> 500 Response + +```json +{ + "data": null, + "error": { + "message": "Internal Server error" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Returns success = true if cart was successfully emptied|[EmptySuccessResponse](#schemaemptysuccessresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no user matching authorization header is found)|[CartResponse](#schemacartresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when authorization header is missing)|[CartResponse](#schemacartresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)| + + + +## post__api_profile_cart_checkout + +> Code samples + +```shell +# You can also use wget +curl -X POST /api/profile/cart/checkout \ + -H 'Accept: application/json' \ + -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + +``` + +```http +POST /api/profile/cart/checkout HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```javascript + +const headers = { + 'Accept':'application/json', + 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +}; + +fetch('/api/profile/cart/checkout', +{ + method: 'POST', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +```ruby +require 'rest-client' +require 'json' + +headers = { + 'Accept' => 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +result = RestClient.post '/api/profile/cart/checkout', + params: { + }, headers: headers + +p JSON.parse(result) + +``` + +```python +import requests +headers = { + 'Accept': 'application/json', + 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +r = requests.post('/api/profile/cart/checkout', headers = headers) + +print(r.json()) + +``` + +```php + 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', +); + +$client = new \GuzzleHttp\Client(); + +// Define array of request body. +$request_body = array(); + +try { + $response = $client->request('POST','/api/profile/cart/checkout', array( + 'headers' => $headers, + 'json' => $request_body, + ) + ); + print_r($response->getBody()->getContents()); + } + catch (\GuzzleHttp\Exception\BadResponseException $e) { + // handle exception or api errors. + print_r($e->getMessage()); + } + + // ... + +``` + +```java +URL obj = new URL("/api/profile/cart/checkout"); +HttpURLConnection con = (HttpURLConnection) obj.openConnection(); +con.setRequestMethod("POST"); +int responseCode = con.getResponseCode(); +BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); +String inputLine; +StringBuffer response = new StringBuffer(); +while ((inputLine = in.readLine()) != null) { + response.append(inputLine); +} +in.close(); +System.out.println(response.toString()); + +``` + +```go +package main + +import ( + "bytes" + "net/http" +) + +func main() { + + headers := map[string][]string{ + "Accept": []string{"application/json"}, + "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, + } + + data := bytes.NewBuffer([]byte{jsonReq}) + req, err := http.NewRequest("POST", "/api/profile/cart/checkout", data) + req.Header = headers + + client := &http.Client{} + resp, err := client.Do(req) + // ... +} + +``` + +`POST /api/profile/cart/checkout` + +*Create an order* + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|x-user-id|header|string(uuid)|true|User id (uuid)| + +> Example responses + +> 200 Response + +```json +{ + "data": { + "order": { + "id": "6c36d6fa-f694-4f9c-9b2c-6f7049d38f4a", + "userId": "6dc52b3c-de7e-431a-84b8-0ec56e0774d4", + "cartId": "cadff0c5-0079-4db8-b6bf-84c9c2633ca3", + "items": [ + { + "product": { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "count": 2 + }, + { + "product": { + "id": "afdd68c4-d359-45e6-b9fd-c8fdb2a162a0", + "title": "Pen", + "description": "Cute pen", + "price": 20 + }, + "count": 5 + } + ], + "payment": { + "type": "paypal", + "address": "London", + "creditCard": "1234-1234-1234-1234" + }, + "delivery": { + "type": "post", + "address": "London" + }, + "comments": "", + "status": "created", + "total": 500 + } + }, + "error": null +} +``` + +> 400 Response + +```json +{ + "data": null, + "error": { + "message": "Cart is empty" + } +} +``` + +> 401 Response + +```json +{ + "data": null, + "error": { + "message": "User is not authorized" + } +} +``` + +> 403 Response + +```json +{ + "data": null, + "error": { + "message": "You must be authorized user" + } +} +``` + +> 500 Response + +```json +{ + "data": null, + "error": { + "message": "Internal Server error" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful operation|[CheckoutResponse](#schemacheckoutresponse)| +|400|[Bad Request](https://tools.ietf.org/html/rfc7231#section-6.5.1)|Bad request|[CartResponse](#schemacartresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no user matching authorization header is found)|[CartResponse](#schemacartresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when authorization header is missing)|[CartResponse](#schemacartresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)| + + + +

product

+ +## get__api_products + +> Code samples + +```shell +# You can also use wget +curl -X GET /api/products \ + -H 'Accept: application/json' \ + -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + +``` + +```http +GET /api/products HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```javascript + +const headers = { + 'Accept':'application/json', + 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +}; + +fetch('/api/products', +{ + method: 'GET', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +```ruby +require 'rest-client' +require 'json' + +headers = { + 'Accept' => 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +result = RestClient.get '/api/products', + params: { + }, headers: headers + +p JSON.parse(result) + +``` + +```python +import requests +headers = { + 'Accept': 'application/json', + 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +r = requests.get('/api/products', headers = headers) + +print(r.json()) + +``` + +```php + 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', +); + +$client = new \GuzzleHttp\Client(); + +// Define array of request body. +$request_body = array(); + +try { + $response = $client->request('GET','/api/products', array( + 'headers' => $headers, + 'json' => $request_body, + ) + ); + print_r($response->getBody()->getContents()); + } + catch (\GuzzleHttp\Exception\BadResponseException $e) { + // handle exception or api errors. + print_r($e->getMessage()); + } + + // ... + +``` + +```java +URL obj = new URL("/api/products"); +HttpURLConnection con = (HttpURLConnection) obj.openConnection(); +con.setRequestMethod("GET"); +int responseCode = con.getResponseCode(); +BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); +String inputLine; +StringBuffer response = new StringBuffer(); +while ((inputLine = in.readLine()) != null) { + response.append(inputLine); +} +in.close(); +System.out.println(response.toString()); + +``` + +```go +package main + +import ( + "bytes" + "net/http" +) + +func main() { + + headers := map[string][]string{ + "Accept": []string{"application/json"}, + "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, + } + + data := bytes.NewBuffer([]byte{jsonReq}) + req, err := http.NewRequest("GET", "/api/products", data) + req.Header = headers + + client := &http.Client{} + resp, err := client.Do(req) + // ... +} + +``` + +`GET /api/products` + +*Returns a list of products* + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|x-user-id|header|string(uuid)|true|User id (uuid)| + +> Example responses + +> 200 Response + +```json +{ + "data": [ + { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + { + "id": "afdd68c4-d359-45e6-b9fd-c8fdb2a162a0", + "title": "Pen", + "description": "Cute pen", + "price": 20 + } + ], + "error": null +} +``` + +> 401 Response + +```json +{ + "data": null, + "error": { + "message": "User is not authorized" + } +} +``` + +> 403 Response + +```json +{ + "data": null, + "error": { + "message": "You must be authorized user" + } +} +``` + +> 500 Response + +```json +{ + "data": null, + "error": { + "message": "Internal Server error" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Returns a list of all products available|Inline| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no user matching authorization header is found)|[CartResponse](#schemacartresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when authorization header is missing)|[CartResponse](#schemacartresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)| + +

Response Schema

+ +Status Code **200** + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|*anonymous*|[[ProductsResponse](#schemaproductsresponse)]|false|none|none| +|» data|[[Product](#schemaproduct)]|false|none|none| +|»» id|string|false|none|Product id (uuid)| +|»» title|string|false|none|Product name| +|»» description|string|false|none|Product description| +|»» price|number|false|none|Product price| +|» error|[ErrorResponse](#schemaerrorresponse)¦null|false|none|none| +|»» message|string|false|none|none| + + + +## get__api_products_{productId} + +> Code samples + +```shell +# You can also use wget +curl -X GET /api/products/{productId} \ + -H 'Accept: application/json' \ + -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' + +``` + +```http +GET /api/products/{productId} HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```javascript + +const headers = { + 'Accept':'application/json', + 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +}; + +fetch('/api/products/{productId}', +{ + method: 'GET', + + headers: headers +}) +.then(function(res) { + return res.json(); +}).then(function(body) { + console.log(body); +}); + +``` + +```ruby +require 'rest-client' +require 'json' + +headers = { + 'Accept' => 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +result = RestClient.get '/api/products/{productId}', + params: { + }, headers: headers + +p JSON.parse(result) + +``` + +```python +import requests +headers = { + 'Accept': 'application/json', + 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' +} + +r = requests.get('/api/products/{productId}', headers = headers) + +print(r.json()) + +``` + +```php + 'application/json', + 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', +); + +$client = new \GuzzleHttp\Client(); + +// Define array of request body. +$request_body = array(); + +try { + $response = $client->request('GET','/api/products/{productId}', array( + 'headers' => $headers, + 'json' => $request_body, + ) + ); + print_r($response->getBody()->getContents()); + } + catch (\GuzzleHttp\Exception\BadResponseException $e) { + // handle exception or api errors. + print_r($e->getMessage()); + } + + // ... + +``` + +```java +URL obj = new URL("/api/products/{productId}"); +HttpURLConnection con = (HttpURLConnection) obj.openConnection(); +con.setRequestMethod("GET"); +int responseCode = con.getResponseCode(); +BufferedReader in = new BufferedReader( + new InputStreamReader(con.getInputStream())); +String inputLine; +StringBuffer response = new StringBuffer(); +while ((inputLine = in.readLine()) != null) { + response.append(inputLine); +} +in.close(); +System.out.println(response.toString()); + +``` + +```go +package main + +import ( + "bytes" + "net/http" +) + +func main() { + + headers := map[string][]string{ + "Accept": []string{"application/json"}, + "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, + } + + data := bytes.NewBuffer([]byte{jsonReq}) + req, err := http.NewRequest("GET", "/api/products/{productId}", data) + req.Header = headers + + client := &http.Client{} + resp, err := client.Do(req) + // ... +} + +``` + +`GET /api/products/{productId}` + +*Returns single product* + +

Parameters

+ +|Name|In|Type|Required|Description| +|---|---|---|---|---| +|productId|path|number|true|Id (uuid) of product to return| +|x-user-id|header|string(uuid)|true|User id (uuid)| + +> Example responses + +> 200 Response + +```json +{ + "data": { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "error": null +} +``` + +> 401 Response + +```json +{ + "data": null, + "error": { + "message": "User is not authorized" + } +} +``` + +> 403 Response + +```json +{ + "data": null, + "error": { + "message": "You must be authorized user" + } +} +``` + +> 404 Response + +```json +{ + "data": null, + "error": { + "message": "No product with such id" + } +} +``` + +> 500 Response + +```json +{ + "data": null, + "error": { + "message": "Internal Server error" + } +} +``` + +

Responses

+ +|Status|Meaning|Description|Schema| +|---|---|---|---| +|200|[OK](https://tools.ietf.org/html/rfc7231#section-6.3.1)|Successful operation|[ProductResponse](#schemaproductresponse)| +|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no user matching authorization header is found)|[CartResponse](#schemacartresponse)| +|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when authorization header is missing)|[CartResponse](#schemacartresponse)| +|404|[Not Found](https://tools.ietf.org/html/rfc7231#section-6.5.4)|Not found|[CartResponse](#schemacartresponse)| +|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)| + + + +# Schemas + +

Cart

+ + + + + + +```json +{ + "id": "dd5ec5ab-deaa-419c-8a6b-7e67b1f7ec87", + "items": [ + { + "product": { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "count": 2 + } + ] +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Cart id (uuid)| +|items|[[CartItem](#schemacartitem)]|false|none|Items added to cart| + +

CartItem

+ + + + + + +```json +{ + "product": { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "count": 2 +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|product|[Product](#schemaproduct)|false|none|none| +|count|number|false|none|Total count of specific products| + +

Product

+ + + + + + +```json +{ + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|Product id (uuid)| +|title|string|false|none|Product name| +|description|string|false|none|Product description| +|price|number|false|none|Product price| + +

Order

+ + + + + + +```json +{ + "id": "string", + "userId": "string", + "cartId": "string", + "items": [ + { + "product": { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "count": 2 + } + ], + "payment": { + "type": "string", + "address": "string", + "creditCard": "string" + }, + "delivery": { + "type": "string", + "address": "string" + }, + "comments": "string", + "status": "string", + "total": 0 +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|id|string|false|none|none| +|userId|string|false|none|none| +|cartId|string|false|none|none| +|items|[[CartItem](#schemacartitem)]|false|none|none| +|payment|object|false|none|none| +|» type|string|false|none|none| +|» address|string|false|none|none| +|» creditCard|string|false|none|none| +|delivery|object|false|none|none| +|» type|string|false|none|none| +|» address|string|false|none|none| +|comments|string|false|none|none| +|status|string|false|none|none| +|total|number|false|none|none| + +

CartResponse

+ + + + + + +```json +{ + "data": { + "cart": { + "id": "dd5ec5ab-deaa-419c-8a6b-7e67b1f7ec87", + "items": [ + { + "product": { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "count": 2 + } + ] + }, + "total": 0 + }, + "error": { + "message": "string" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|data|object|false|none|none| +|» cart|[Cart](#schemacart)|false|none|none| +|» total|number|false|none|none| +|error|[ErrorResponse](#schemaerrorresponse)|false|none|none| + +

CheckoutResponse

+ + + + + + +```json +{ + "data": { + "order": { + "id": "string", + "userId": "string", + "cartId": "string", + "items": [ + { + "product": { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "count": 2 + } + ], + "payment": { + "type": "string", + "address": "string", + "creditCard": "string" + }, + "delivery": { + "type": "string", + "address": "string" + }, + "comments": "string", + "status": "string", + "total": 0 + } + }, + "error": { + "message": "string" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|data|object|false|none|none| +|» order|[Order](#schemaorder)|false|none|none| +|error|[ErrorResponse](#schemaerrorresponse)|false|none|none| + +

ProductResponse

+ + + + + + +```json +{ + "data": { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + }, + "error": { + "message": "string" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|data|[Product](#schemaproduct)|false|none|none| +|error|[ErrorResponse](#schemaerrorresponse)|false|none|none| + +

ProductsResponse

+ + + + + + +```json +{ + "data": [ + { + "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697", + "title": "Book", + "description": "Interesting book", + "price": 200 + } + ], + "error": { + "message": "string" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|data|[[Product](#schemaproduct)]|false|none|none| +|error|[ErrorResponse](#schemaerrorresponse)|false|none|none| + +

UpdateCartRequestBody

+ + + + + + +```json +{ + "productId": "915b2f40-9fd9-47f2-9b51-628f3dc69aac", + "count": 5 +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|productId|string|false|none|none| +|count|number|false|none|none| + +

UserCredentials

+ + + + + + +```json +{ + "email": "ann.jones@epam.com", + "password": "DDQldls?kdpw0fk" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|email|string|false|none|none| +|password|string|false|none|none| + +

EmptySuccessResponse

+ + + + + + +```json +{ + "data": { + "success": true + }, + "error": { + "message": "string" + } +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|data|object|false|none|none| +|» success|boolean|false|none|none| +|error|[ErrorResponse](#schemaerrorresponse)|false|none|none| + +

ErrorResponse

+ + + + + + +```json +{ + "message": "string" +} + +``` + +### Properties + +|Name|Type|Required|Restrictions|Description| +|---|---|---|---|---| +|message|string|false|none|none| -| Code | Description | -| ---- | ----------- | -| 200 | Returns new user | -| 404 | Bad request | -| 500 | Internal server error | diff --git a/swagger.yaml b/swagger.yaml index ad538e7..e715915 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -5,7 +5,6 @@ info: tags: - name: cart - name: product - - name: auth paths: /api/profile/cart: get: @@ -442,83 +441,6 @@ paths: data: null error: message: 'Internal Server error' - /api/auth/register: - post: - tags: - - auth - summary: Create new user (for Module 9) - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUserRequestBody' - responses: - '200': - description: Returns new user - content: - application/json: - schema: - $ref: '#/components/schemas/CreateUserResponse' - example: - data: - id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - email: ann.jones@epam.com - password: 1234kdksla020-1444 - role: admin - error: null - '400': - description: Bad request - content: - application/json: - example: - data: null - error: - message: Email is not valid - '500': - description: Internal server error - content: - application/json: - example: - data: null - error: - message: 'Internal Server error' - /api/auth/login: - post: - tags: - - auth - summary: Login user (for Module 9) - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/UserCredentials' - responses: - '200': - description: Returns new user - content: - application/json: - schema: - $ref: '#/components/schemas/LoginUserResponse' - example: - data: - token: VugRyl8SX88-LOZhvJxupjfWX=rATjop1kFw/NcshAQ4QdX0moMh9w9pVtL4rowLVoVpjpYBSKBUJsfqjnjnr-ImD/XAWJUdXIFfy3!7ViC/pxSP5zNefrgMAt2=?3=iv7Gga=zjphHqog6Jdy?AliWwYROxe4AUzQAxM!Nc=Hxax-0hJRJ!ijgE-=6RFis!cuHXxpJBA=EpoTYmla247q6rrm1RA!WLq3rJOX1itwD-LGb8hdGVXwGFgxy9QNJ/ - error: null - '404': - description: Bad request - content: - application/json: - example: - data: null - error: - message: No user with such email or password - '500': - description: Internal server error - content: - application/json: - example: - data: null - error: - message: 'Internal Server error' components: schemas: Cart: @@ -652,29 +574,6 @@ components: password: type: string example: "DDQldls?kdpw0fk" - CreateUserRequestBody: - allOf: - - $ref: '#/components/schemas/UserCredentials' - - type: object - properties: - role: - type: string - enum: [admin, user] - example: "admin" - CreateUserResponse: - allOf: - - $ref: '#/components/schemas/CreateUserRequestBody' - - type: object - properties: - id: - type: string - example: "fdbbb574-266b-4b2e-ad2d-790bcf4765c2" - LoginUserResponse: - type: object - properties: - token: - type: string - example: "VugRyl8SX88-LOZhvJxupjfWX=rATjop1kFw/NcshAQ4QdX0moMh9w9pVtL4rowLVoVpjpYBSKBUJsfqjnjnr-ImD/XAWJUdXIFfy3!7ViC/pxSP5zNefrgMAt2=?3=iv7Gga=zjphHqog6Jdy?AliWwYROxe4AUzQAxM!Nc=Hxax-0hJRJ!ijgE-=6RFis!cuHXxpJBA=EpoTYmla247q6rrm1RA!WLq3rJOX1itwD-LGb8hdGVXwGFgxy9QNJ/" EmptySuccessResponse: type: object properties: From 7a71222afb6df6b70add4a1e8796bb23e62cffd1 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 13:58:52 +0600 Subject: [PATCH 11/17] Fix swagger --- swagger.md | 10 +++++----- swagger.yaml | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/swagger.md b/swagger.md index a5b8f11..24c46c6 100644 --- a/swagger.md +++ b/swagger.md @@ -1,5 +1,5 @@ --- -title: Node.js GMP Express application v1.0.0 +title: API v1.0.0 language_tabs: - shell: Shell - http: HTTP @@ -19,11 +19,11 @@ headingLevel: 2 -

Node.js GMP Express application v1.0.0

+

null v1.0.0

> Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu. -

cart

+

cart

## get__api_profile_cart @@ -1005,7 +1005,7 @@ func main() { This operation does not require authentication -

product

+

product

## get__api_products @@ -1811,7 +1811,7 @@ This operation does not require authentication ```json { - "email": "ann.jones@epam.com", + "email": "ann.jones@gmail.com", "password": "DDQldls?kdpw0fk" } diff --git a/swagger.yaml b/swagger.yaml index e715915..d0906d7 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -1,6 +1,6 @@ openapi: 3.0.3 info: - title: Node.js GMP Express application + title: version: 1.0.0 tags: - name: cart @@ -570,7 +570,7 @@ components: properties: email: type: string - example: "ann.jones@epam.com" + example: "ann.jones@gmail.com" password: type: string example: "DDQldls?kdpw0fk" From df15ec2c75538514abf00514fc867496023c3563 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 14:10:17 +0600 Subject: [PATCH 12/17] Fix swagger --- swagger.md | 879 +++-------------------------------------------------- 1 file changed, 50 insertions(+), 829 deletions(-) diff --git a/swagger.md b/swagger.md index 24c46c6..6e2e627 100644 --- a/swagger.md +++ b/swagger.md @@ -1,17 +1,14 @@ --- title: API v1.0.0 language_tabs: - - shell: Shell - - http: HTTP - - javascript: JavaScript - - ruby: Ruby - - python: Python - - php: PHP - - java: Java - - go: Go + - http: http + - HTTP: HTTP +language_clients: + - http: "" + - HTTP: "" toc_footers: [] includes: [] -search: true +search: false highlight_theme: darkula headingLevel: 2 @@ -25,18 +22,10 @@ headingLevel: 2

cart

-## get__api_profile_cart +## Get user cart or create if it is missing > Code samples -```shell -# You can also use wget -curl -X GET /api/profile/cart \ - -H 'Accept: application/json' \ - -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' - -``` - ```http GET /api/profile/cart HTTP/1.1 @@ -45,137 +34,17 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` -```javascript - -const headers = { - 'Accept':'application/json', - 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -}; - -fetch('/api/profile/cart', -{ - method: 'GET', - - headers: headers -}) -.then(function(res) { - return res.json(); -}).then(function(body) { - console.log(body); -}); - -``` - -```ruby -require 'rest-client' -require 'json' - -headers = { - 'Accept' => 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -result = RestClient.get '/api/profile/cart', - params: { - }, headers: headers - -p JSON.parse(result) - -``` - -```python -import requests -headers = { - 'Accept': 'application/json', - 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -r = requests.get('/api/profile/cart', headers = headers) - -print(r.json()) - -``` - -```php - 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', -); - -$client = new \GuzzleHttp\Client(); - -// Define array of request body. -$request_body = array(); - -try { - $response = $client->request('GET','/api/profile/cart', array( - 'headers' => $headers, - 'json' => $request_body, - ) - ); - print_r($response->getBody()->getContents()); - } - catch (\GuzzleHttp\Exception\BadResponseException $e) { - // handle exception or api errors. - print_r($e->getMessage()); - } - - // ... - -``` - -```java -URL obj = new URL("/api/profile/cart"); -HttpURLConnection con = (HttpURLConnection) obj.openConnection(); -con.setRequestMethod("GET"); -int responseCode = con.getResponseCode(); -BufferedReader in = new BufferedReader( - new InputStreamReader(con.getInputStream())); -String inputLine; -StringBuffer response = new StringBuffer(); -while ((inputLine = in.readLine()) != null) { - response.append(inputLine); -} -in.close(); -System.out.println(response.toString()); - -``` - -```go -package main - -import ( - "bytes" - "net/http" -) - -func main() { - - headers := map[string][]string{ - "Accept": []string{"application/json"}, - "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, - } - - data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("GET", "/api/profile/cart", data) - req.Header = headers +```http +GET /api/profile/cart HTTP/1.1 - client := &http.Client{} - resp, err := client.Do(req) - // ... -} +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` `GET /api/profile/cart` -*Get user cart or create if it is missing* - -

Parameters

+

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -241,7 +110,7 @@ func main() { } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -254,19 +123,10 @@ func main() { This operation does not require authentication -## put__api_profile_cart +## Update user cart > Code samples -```shell -# You can also use wget -curl -X PUT /api/profile/cart \ - -H 'Content-Type: application/json' \ - -H 'Accept: application/json' \ - -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' - -``` - ```http PUT /api/profile/cart HTTP/1.1 @@ -276,144 +136,17 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` -```javascript -const inputBody = '{ - "productId": "915b2f40-9fd9-47f2-9b51-628f3dc69aac", - "count": 5 -}'; -const headers = { - 'Content-Type':'application/json', - 'Accept':'application/json', - 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -}; - -fetch('/api/profile/cart', -{ - method: 'PUT', - body: inputBody, - headers: headers -}) -.then(function(res) { - return res.json(); -}).then(function(body) { - console.log(body); -}); - -``` - -```ruby -require 'rest-client' -require 'json' - -headers = { - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -result = RestClient.put '/api/profile/cart', - params: { - }, headers: headers - -p JSON.parse(result) - -``` - -```python -import requests -headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -r = requests.put('/api/profile/cart', headers = headers) - -print(r.json()) - -``` - -```php - 'application/json', - 'Accept' => 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', -); - -$client = new \GuzzleHttp\Client(); - -// Define array of request body. -$request_body = array(); - -try { - $response = $client->request('PUT','/api/profile/cart', array( - 'headers' => $headers, - 'json' => $request_body, - ) - ); - print_r($response->getBody()->getContents()); - } - catch (\GuzzleHttp\Exception\BadResponseException $e) { - // handle exception or api errors. - print_r($e->getMessage()); - } - - // ... - -``` - -```java -URL obj = new URL("/api/profile/cart"); -HttpURLConnection con = (HttpURLConnection) obj.openConnection(); -con.setRequestMethod("PUT"); -int responseCode = con.getResponseCode(); -BufferedReader in = new BufferedReader( - new InputStreamReader(con.getInputStream())); -String inputLine; -StringBuffer response = new StringBuffer(); -while ((inputLine = in.readLine()) != null) { - response.append(inputLine); -} -in.close(); -System.out.println(response.toString()); - -``` - -```go -package main - -import ( - "bytes" - "net/http" -) - -func main() { - - headers := map[string][]string{ - "Content-Type": []string{"application/json"}, - "Accept": []string{"application/json"}, - "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, - } - - data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("PUT", "/api/profile/cart", data) - req.Header = headers +```http +PUT /api/profile/cart HTTP/1.1 - client := &http.Client{} - resp, err := client.Do(req) - // ... -} +Content-Type: application/json +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` `PUT /api/profile/cart` -*Update user cart* - > Body parameter ```json @@ -423,7 +156,7 @@ func main() { } ``` -

Parameters

+

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -512,7 +245,7 @@ func main() { } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -527,18 +260,10 @@ func main() { This operation does not require authentication -## delete__api_profile_cart +## Empty user cart > Code samples -```shell -# You can also use wget -curl -X DELETE /api/profile/cart \ - -H 'Accept: application/json' \ - -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' - -``` - ```http DELETE /api/profile/cart HTTP/1.1 @@ -547,137 +272,17 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` -```javascript - -const headers = { - 'Accept':'application/json', - 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -}; - -fetch('/api/profile/cart', -{ - method: 'DELETE', - - headers: headers -}) -.then(function(res) { - return res.json(); -}).then(function(body) { - console.log(body); -}); - -``` - -```ruby -require 'rest-client' -require 'json' - -headers = { - 'Accept' => 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -result = RestClient.delete '/api/profile/cart', - params: { - }, headers: headers - -p JSON.parse(result) - -``` - -```python -import requests -headers = { - 'Accept': 'application/json', - 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -r = requests.delete('/api/profile/cart', headers = headers) - -print(r.json()) - -``` - -```php - 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', -); - -$client = new \GuzzleHttp\Client(); - -// Define array of request body. -$request_body = array(); - -try { - $response = $client->request('DELETE','/api/profile/cart', array( - 'headers' => $headers, - 'json' => $request_body, - ) - ); - print_r($response->getBody()->getContents()); - } - catch (\GuzzleHttp\Exception\BadResponseException $e) { - // handle exception or api errors. - print_r($e->getMessage()); - } - - // ... - -``` - -```java -URL obj = new URL("/api/profile/cart"); -HttpURLConnection con = (HttpURLConnection) obj.openConnection(); -con.setRequestMethod("DELETE"); -int responseCode = con.getResponseCode(); -BufferedReader in = new BufferedReader( - new InputStreamReader(con.getInputStream())); -String inputLine; -StringBuffer response = new StringBuffer(); -while ((inputLine = in.readLine()) != null) { - response.append(inputLine); -} -in.close(); -System.out.println(response.toString()); - -``` - -```go -package main - -import ( - "bytes" - "net/http" -) - -func main() { - - headers := map[string][]string{ - "Accept": []string{"application/json"}, - "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, - } - - data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("DELETE", "/api/profile/cart", data) - req.Header = headers +```http +DELETE /api/profile/cart HTTP/1.1 - client := &http.Client{} - resp, err := client.Do(req) - // ... -} +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` `DELETE /api/profile/cart` -*Empty user cart* - -

Parameters

+

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -729,7 +334,7 @@ func main() { } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -742,18 +347,10 @@ func main() { This operation does not require authentication -## post__api_profile_cart_checkout +## Create an order > Code samples -```shell -# You can also use wget -curl -X POST /api/profile/cart/checkout \ - -H 'Accept: application/json' \ - -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' - -``` - ```http POST /api/profile/cart/checkout HTTP/1.1 @@ -762,137 +359,17 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` -```javascript - -const headers = { - 'Accept':'application/json', - 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -}; - -fetch('/api/profile/cart/checkout', -{ - method: 'POST', - - headers: headers -}) -.then(function(res) { - return res.json(); -}).then(function(body) { - console.log(body); -}); - -``` - -```ruby -require 'rest-client' -require 'json' - -headers = { - 'Accept' => 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -result = RestClient.post '/api/profile/cart/checkout', - params: { - }, headers: headers - -p JSON.parse(result) - -``` - -```python -import requests -headers = { - 'Accept': 'application/json', - 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -r = requests.post('/api/profile/cart/checkout', headers = headers) - -print(r.json()) - -``` - -```php - 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', -); - -$client = new \GuzzleHttp\Client(); - -// Define array of request body. -$request_body = array(); - -try { - $response = $client->request('POST','/api/profile/cart/checkout', array( - 'headers' => $headers, - 'json' => $request_body, - ) - ); - print_r($response->getBody()->getContents()); - } - catch (\GuzzleHttp\Exception\BadResponseException $e) { - // handle exception or api errors. - print_r($e->getMessage()); - } - - // ... - -``` - -```java -URL obj = new URL("/api/profile/cart/checkout"); -HttpURLConnection con = (HttpURLConnection) obj.openConnection(); -con.setRequestMethod("POST"); -int responseCode = con.getResponseCode(); -BufferedReader in = new BufferedReader( - new InputStreamReader(con.getInputStream())); -String inputLine; -StringBuffer response = new StringBuffer(); -while ((inputLine = in.readLine()) != null) { - response.append(inputLine); -} -in.close(); -System.out.println(response.toString()); - -``` - -```go -package main - -import ( - "bytes" - "net/http" -) - -func main() { - - headers := map[string][]string{ - "Accept": []string{"application/json"}, - "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, - } - - data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("POST", "/api/profile/cart/checkout", data) - req.Header = headers +```http +POST /api/profile/cart/checkout HTTP/1.1 - client := &http.Client{} - resp, err := client.Do(req) - // ... -} +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` `POST /api/profile/cart/checkout` -*Create an order* - -

Parameters

+

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -991,7 +468,7 @@ func main() { } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -1007,18 +484,10 @@ This operation does not require authentication

product

-## get__api_products +## Returns a list of products > Code samples -```shell -# You can also use wget -curl -X GET /api/products \ - -H 'Accept: application/json' \ - -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' - -``` - ```http GET /api/products HTTP/1.1 @@ -1027,137 +496,17 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` -```javascript - -const headers = { - 'Accept':'application/json', - 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -}; - -fetch('/api/products', -{ - method: 'GET', - - headers: headers -}) -.then(function(res) { - return res.json(); -}).then(function(body) { - console.log(body); -}); - -``` - -```ruby -require 'rest-client' -require 'json' - -headers = { - 'Accept' => 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -result = RestClient.get '/api/products', - params: { - }, headers: headers - -p JSON.parse(result) - -``` - -```python -import requests -headers = { - 'Accept': 'application/json', - 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -r = requests.get('/api/products', headers = headers) - -print(r.json()) - -``` - -```php - 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', -); - -$client = new \GuzzleHttp\Client(); - -// Define array of request body. -$request_body = array(); - -try { - $response = $client->request('GET','/api/products', array( - 'headers' => $headers, - 'json' => $request_body, - ) - ); - print_r($response->getBody()->getContents()); - } - catch (\GuzzleHttp\Exception\BadResponseException $e) { - // handle exception or api errors. - print_r($e->getMessage()); - } - - // ... - -``` - -```java -URL obj = new URL("/api/products"); -HttpURLConnection con = (HttpURLConnection) obj.openConnection(); -con.setRequestMethod("GET"); -int responseCode = con.getResponseCode(); -BufferedReader in = new BufferedReader( - new InputStreamReader(con.getInputStream())); -String inputLine; -StringBuffer response = new StringBuffer(); -while ((inputLine = in.readLine()) != null) { - response.append(inputLine); -} -in.close(); -System.out.println(response.toString()); - -``` - -```go -package main - -import ( - "bytes" - "net/http" -) - -func main() { - - headers := map[string][]string{ - "Accept": []string{"application/json"}, - "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, - } - - data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("GET", "/api/products", data) - req.Header = headers +```http +GET /api/products HTTP/1.1 - client := &http.Client{} - resp, err := client.Do(req) - // ... -} +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` `GET /api/products` -*Returns a list of products* - -

Parameters

+

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -1220,7 +569,7 @@ func main() { } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -1229,7 +578,7 @@ func main() { |403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when authorization header is missing)|[CartResponse](#schemacartresponse)| |500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)| -

Response Schema

+

Response Schema

Status Code **200** @@ -1248,18 +597,10 @@ Status Code **200** This operation does not require authentication -## get__api_products_{productId} +## Returns single product > Code samples -```shell -# You can also use wget -curl -X GET /api/products/{productId} \ - -H 'Accept: application/json' \ - -H 'x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' - -``` - ```http GET /api/products/{productId} HTTP/1.1 @@ -1268,137 +609,17 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` -```javascript - -const headers = { - 'Accept':'application/json', - 'x-user-id':'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -}; - -fetch('/api/products/{productId}', -{ - method: 'GET', - - headers: headers -}) -.then(function(res) { - return res.json(); -}).then(function(body) { - console.log(body); -}); - -``` - -```ruby -require 'rest-client' -require 'json' - -headers = { - 'Accept' => 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -result = RestClient.get '/api/products/{productId}', - params: { - }, headers: headers - -p JSON.parse(result) - -``` - -```python -import requests -headers = { - 'Accept': 'application/json', - 'x-user-id': 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c' -} - -r = requests.get('/api/products/{productId}', headers = headers) - -print(r.json()) - -``` - -```php - 'application/json', - 'x-user-id' => 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c', -); - -$client = new \GuzzleHttp\Client(); - -// Define array of request body. -$request_body = array(); - -try { - $response = $client->request('GET','/api/products/{productId}', array( - 'headers' => $headers, - 'json' => $request_body, - ) - ); - print_r($response->getBody()->getContents()); - } - catch (\GuzzleHttp\Exception\BadResponseException $e) { - // handle exception or api errors. - print_r($e->getMessage()); - } - - // ... - -``` - -```java -URL obj = new URL("/api/products/{productId}"); -HttpURLConnection con = (HttpURLConnection) obj.openConnection(); -con.setRequestMethod("GET"); -int responseCode = con.getResponseCode(); -BufferedReader in = new BufferedReader( - new InputStreamReader(con.getInputStream())); -String inputLine; -StringBuffer response = new StringBuffer(); -while ((inputLine = in.readLine()) != null) { - response.append(inputLine); -} -in.close(); -System.out.println(response.toString()); - -``` - -```go -package main - -import ( - "bytes" - "net/http" -) - -func main() { - - headers := map[string][]string{ - "Accept": []string{"application/json"}, - "x-user-id": []string{"eb5a26af-6e4c-4f31-a9b1-3450d42ac66c"}, - } - - data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("GET", "/api/products/{productId}", data) - req.Header = headers +```http +GET /api/products/{productId} HTTP/1.1 - client := &http.Client{} - resp, err := client.Do(req) - // ... -} +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c ``` `GET /api/products/{productId}` -*Returns single product* - -

Parameters

+

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -1465,7 +686,7 @@ func main() { } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| From c0424067a84b8fea8c23da7415899b4ebbe3f51c Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 14:17:49 +0600 Subject: [PATCH 13/17] Fix swagger --- swagger.md | 157 ++++++++++++----------------------------------------- 1 file changed, 34 insertions(+), 123 deletions(-) diff --git a/swagger.md b/swagger.md index 6e2e627..a37799b 100644 --- a/swagger.md +++ b/swagger.md @@ -1,11 +1,9 @@ --- title: API v1.0.0 language_tabs: - - http: http - - HTTP: HTTP + - "'http": HTTP' language_clients: - - http: "" - - HTTP: "" + - "'http": "" toc_footers: [] includes: [] search: false @@ -16,35 +14,21 @@ headingLevel: 2 -

null v1.0.0

+

API v1.0.0

> Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu.

cart

-## Get user cart or create if it is missing +## get__api_profile_cart > Code samples -```http -GET /api/profile/cart HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - -```http -GET /api/profile/cart HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - `GET /api/profile/cart` -

Parameters

+*Get user cart or create if it is missing* + +

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -110,7 +94,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -123,30 +107,14 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c This operation does not require authentication -## Update user cart +## put__api_profile_cart > Code samples -```http -PUT /api/profile/cart HTTP/1.1 - -Content-Type: application/json -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - -```http -PUT /api/profile/cart HTTP/1.1 - -Content-Type: application/json -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - `PUT /api/profile/cart` +*Update user cart* + > Body parameter ```json @@ -156,7 +124,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c } ``` -

Parameters

+

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -245,7 +213,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -260,29 +228,15 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c This operation does not require authentication -## Empty user cart +## delete__api_profile_cart > Code samples -```http -DELETE /api/profile/cart HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - -```http -DELETE /api/profile/cart HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - `DELETE /api/profile/cart` -

Parameters

+*Empty user cart* + +

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -334,7 +288,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -347,29 +301,15 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c This operation does not require authentication -## Create an order +## post__api_profile_cart_checkout > Code samples -```http -POST /api/profile/cart/checkout HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - -```http -POST /api/profile/cart/checkout HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - `POST /api/profile/cart/checkout` -

Parameters

+*Create an order* + +

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -468,7 +408,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -484,29 +424,15 @@ This operation does not require authentication

product

-## Returns a list of products +## get__api_products > Code samples -```http -GET /api/products HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - -```http -GET /api/products HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - `GET /api/products` -

Parameters

+*Returns a list of products* + +

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -569,7 +495,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -578,7 +504,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c |403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when authorization header is missing)|[CartResponse](#schemacartresponse)| |500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)| -

Response Schema

+

Response Schema

Status Code **200** @@ -597,29 +523,15 @@ Status Code **200** This operation does not require authentication -## Returns single product +## get__api_products_{productId} > Code samples -```http -GET /api/products/{productId} HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - -```http -GET /api/products/{productId} HTTP/1.1 - -Accept: application/json -x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c - -``` - `GET /api/products/{productId}` -

Parameters

+*Returns single product* + +

Parameters

|Name|In|Type|Required|Description| |---|---|---|---|---| @@ -686,7 +598,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c } ``` -

Responses

+

Responses

|Status|Meaning|Description|Schema| |---|---|---|---| @@ -1091,4 +1003,3 @@ This operation does not require authentication |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| |message|string|false|none|none| - From 648dba3057f5a8e762efac5d90ccf16d6a263f2c Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 14:21:41 +0600 Subject: [PATCH 14/17] Fix swagger --- swagger.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/swagger.md b/swagger.md index a37799b..6e1231d 100644 --- a/swagger.md +++ b/swagger.md @@ -1,9 +1,11 @@ --- title: API v1.0.0 language_tabs: - - "'http": HTTP' + - http: http + - HTTP: HTTP language_clients: - - "'http": "" + - http: "" + - HTTP: "" toc_footers: [] includes: [] search: false @@ -14,7 +16,7 @@ headingLevel: 2 -

API v1.0.0

+

null v1.0.0

> Scroll down for code samples, example requests and responses. Select a language for code samples from the tabs above or the mobile navigation menu. @@ -24,6 +26,22 @@ headingLevel: 2 > Code samples +```http +GET /api/profile/cart HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```http +GET /api/profile/cart HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + `GET /api/profile/cart` *Get user cart or create if it is missing* @@ -111,6 +129,24 @@ This operation does not require authentication > Code samples +```http +PUT /api/profile/cart HTTP/1.1 + +Content-Type: application/json +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```http +PUT /api/profile/cart HTTP/1.1 + +Content-Type: application/json +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + `PUT /api/profile/cart` *Update user cart* @@ -232,6 +268,22 @@ This operation does not require authentication > Code samples +```http +DELETE /api/profile/cart HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```http +DELETE /api/profile/cart HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + `DELETE /api/profile/cart` *Empty user cart* @@ -305,6 +357,22 @@ This operation does not require authentication > Code samples +```http +POST /api/profile/cart/checkout HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```http +POST /api/profile/cart/checkout HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + `POST /api/profile/cart/checkout` *Create an order* @@ -428,6 +496,22 @@ This operation does not require authentication > Code samples +```http +GET /api/products HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```http +GET /api/products HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + `GET /api/products` *Returns a list of products* @@ -527,6 +611,22 @@ This operation does not require authentication > Code samples +```http +GET /api/products/{productId} HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + +```http +GET /api/products/{productId} HTTP/1.1 + +Accept: application/json +x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c + +``` + `GET /api/products/{productId}` *Returns single product* @@ -1003,3 +1103,4 @@ This operation does not require authentication |Name|Type|Required|Restrictions|Description| |---|---|---|---|---| |message|string|false|none|none| + From 7a944ad7a8d95eb28902798d1fb1f86a26b7bffb Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 12:23:27 +0400 Subject: [PATCH 15/17] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index a48d004..8cae447 100644 --- a/README.md +++ b/README.md @@ -33,4 +33,7 @@ One User can have multiple Order. Each Order From 27a8f8bf915eeef5a633682db6238bcf43ec9dff Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 17:49:55 +0600 Subject: [PATCH 16/17] Fix db export --- src/db/db.js | 9 +++++++++ src/index.ts | 9 --------- src/repository/cart.repository.ts | 2 +- src/repository/order.repository.ts | 2 +- src/repository/product.repository.ts | 2 +- src/repository/user.repository.ts | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) create mode 100644 src/db/db.js diff --git a/src/db/db.js b/src/db/db.js new file mode 100644 index 0000000..9f039a3 --- /dev/null +++ b/src/db/db.js @@ -0,0 +1,9 @@ +import { products } from "./product.db.js" +import { carts } from "./cart.db.js" +import { orders } from "./order.db.js" +import { users } from "./user.db.js"; + +export const userDB = users; +export const productDB = products; +export const cartDB = carts; +export const orderDB = orders; diff --git a/src/index.ts b/src/index.ts index 71d0a90..4071da3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,3 @@ -import { users } from "./db/user.db.js" -import { products } from "./db/product.db.js" -import { carts } from "./db/cart.db.js" -import { orders } from "./db/order.db.js" import express, { NextFunction, Request, Response } from "express"; import { findUser } from "./repository/user.repository.ts"; import cartRouter from "./controller/cart.controller.ts"; @@ -9,11 +5,6 @@ import productRouter from "./controller/product.controller.ts"; import { ResponseError, getForbidenError, getUnauthorizedError } from "./utils/errors.ts"; import { getResponseError } from "./utils/utils.ts"; -export const userDB = users; -export const productDB = products; -export const cartDB = carts; -export const orderDB = orders; - export const app = express(); const PORT = 3000; diff --git a/src/repository/cart.repository.ts b/src/repository/cart.repository.ts index 8406637..7218331 100644 --- a/src/repository/cart.repository.ts +++ b/src/repository/cart.repository.ts @@ -1,4 +1,4 @@ -import { cartDB } from ".."; +import { cartDB } from "../db/db"; import { CartEditableProperties, ICart, ICartItem } from "../scheme/CartScheme"; export const pushCart = async (cart: ICart) => { diff --git a/src/repository/order.repository.ts b/src/repository/order.repository.ts index 2593aa0..ea45c75 100644 --- a/src/repository/order.repository.ts +++ b/src/repository/order.repository.ts @@ -1,4 +1,4 @@ -import { orderDB } from ".."; +import { orderDB } from "../db/db"; import { IOrder } from "../scheme/OrderScheme"; export const pushOrder = async (order: IOrder): Promise => { diff --git a/src/repository/product.repository.ts b/src/repository/product.repository.ts index 901a0f0..32ff596 100644 --- a/src/repository/product.repository.ts +++ b/src/repository/product.repository.ts @@ -1,4 +1,4 @@ -import { productDB } from ".."; +import { productDB } from "../db/db"; import { IProduct } from "../scheme/ProductScheme"; export const findProduct = async (id: string): Promise => { diff --git a/src/repository/user.repository.ts b/src/repository/user.repository.ts index 9dba456..6651535 100644 --- a/src/repository/user.repository.ts +++ b/src/repository/user.repository.ts @@ -1,4 +1,4 @@ -import { userDB } from ".."; +import { userDB } from "../db/db"; export const findUser = async (userId: string) => { return await userDB.find((id) => id === userId); From bff1a61ec3736549843eee1b89dd82f05f8a5b83 Mon Sep 17 00:00:00 2001 From: Leyla Kandalova Date: Fri, 13 Oct 2023 15:57:47 +0400 Subject: [PATCH 17/17] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 8cae447..720bded 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/express-layered-architecture 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. + The application has 4 primary entities: User - can add some products to the cart and then order them ([scheme](https://github.com/kandalova/node-mentoring-shop/blob/task_6_shop_express.js/src/scheme/UserScheme.ts)).