diff --git a/README.md b/README.md
index 947daac..aac3325 100644
--- a/README.md
+++ b/README.md
@@ -1,44 +1,19 @@
# node-mentoring-shop
-[https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/nosql/homework](https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/nosql/homework)
+[https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/authorization/homework](https://d17btkcdsmqrmh.cloudfront.net/node-gmp/docs/authorization/homework)
We are going to create an Express application for online shop which sells different types of products (like e.g Amazon).
-In this task we will modify the application we created in [Express and Layered Architecture](https://github.com/kandalova/node-mentoring-shop/pull/1) module by moving data storage to NoSQL database.
-
-The application has 4 primary entities:
-
-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.
+In this task we will need to modify existing [Express application](https://github.com/kandalova/node-mentoring-shop/pull/3) by extending user model, adding authorization and authentication flows.
**Implementation criteria:**
-- TypeScript is used.
-- Data is stored in `MongoDB` database.
-- `Docker` image is used for local development (check Running [MongoDB as a Docker Container](https://www.baeldung.com/linux/mongodb-as-docker-container#2-building-container-using-a-compose-file) for an example of docker-compose file).
-- `Mongoose` is used as ODM for querying.
-- `Data Access Layer` is rewritten to query `MongoDB`.
-- Models are created based on entity schemas used in Express and Layered Architecture module.
-- Models have proper relations between each other based on information specified above.
-
-**API endpoints**
-
-[swagger](https://github.com/kandalova/node-mentoring-shop/blob/task_6_shop_express.js/swagger.md)
-
-
+- User entity is added - contains id, email (unique), password, role (admin or simple user). Password is stored as hashed value in the database.
+- [Bcrypt](https://www.npmjs.com/package/bcrypt) module is used for hashing passwords.
+- New API endpoint is added for user **sign up** by email and password e.g `/register`. It creates user entity in the database.
+- New API endpoint is added for user **sign in** by email and password e.g `/login`. It returns JWT token which contains user information. Pay attention that password is not encoded in token payload. JWT token expires in 2 hours.
+- JWT token is passed in `Authorization` header for each request (except sign in and sign up) in the following format `Authorization: Bearer `
+- Authentication middleware is added to check if token provided is valid and if user encoded in token exists. If no, `401 Unauthorized` status code is returned. Otherwise, user can do action they intended to do.
+- Only admin users can delete user cart. Authorization middleware is added for this purpose. If token provided doesn't belong to admin member, `403 Forbidden` status code is returned.
**Setup for startring (once)**
@@ -60,8 +35,26 @@ In the project root folder
**First time starting**
- Create your first database in MongoDB. Then, create your first collection (Example - User).
+- It's possible to use seeder in `index.ts` to mock `Product` model.
+- `Register` and `login` user, use returned token in requests's headers.
+
+**API endpoints**
+
+[swagger](https://github.com/kandalova/node-mentoring-shop/blob/task_9_authorization/swagger.md)
+
+
+
+**How to convert `yaml` to `md`**
+
+`widdershins swagger.yaml --language_tabs 'http:HTTP' -o swagger.md`
**Results**
-
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
index abf2a2e..d863849 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,15 +9,20 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "bcryptjs": "^2.4.3",
+ "dotenv": "^16.3.1",
"express": "^4.18.2",
"joi": "^17.11.0",
+ "jsonwebtoken": "^9.0.2",
"lodash.clonedeep": "^4.5.0",
"mongodb": "^6.2.0",
"mongoose": "^7.6.4",
"uuid": "^9.0.1"
},
"devDependencies": {
+ "@types/bcryptjs": "^2.4.5",
"@types/express": "^4.17.18",
+ "@types/jsonwebtoken": "^9.0.4",
"@types/lodash.clonedeep": "^4.5.7",
"@types/mongoose": "^5.11.97",
"@types/node": "^20.8.10",
@@ -309,6 +314,12 @@
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
},
+ "node_modules/@types/bcryptjs": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.5.tgz",
+ "integrity": "sha512-tOF6TivOIvq+TWQm78335CMdyVJhpBG3NUdWQDAp95ax4E2rSKbws/ELHLk5EBoucwx/tHt3/hhLOHwWJgVrSw==",
+ "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",
@@ -364,6 +375,15 @@
"integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==",
"dev": true
},
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.4",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.4.tgz",
+ "integrity": "sha512-8UYapdmR0QlxgvJmyE8lP7guxD0UGVMfknsdtCFZh4ovShdBl3iOI4zdvqBHrB/IS+xUj3PSx73Qkey1fhWz+g==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
"node_modules/@types/lodash": {
"version": "4.14.199",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.199.tgz",
@@ -876,6 +896,11 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
+ "node_modules/bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
+ },
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
@@ -951,6 +976,11 @@
"node": ">=16.20.1"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -1185,6 +1215,25 @@
"node": ">=6.0.0"
}
},
+ "node_modules/dotenv": {
+ "version": "16.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
+ "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@@ -1946,6 +1995,46 @@
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+ "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+ "dependencies": {
+ "jws": "^3.2.2",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+ "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+ "dependencies": {
+ "buffer-equal-constant-time": "1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+ "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+ "dependencies": {
+ "jwa": "^1.4.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/kareem": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
@@ -1996,17 +2085,51 @@
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
},
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+ },
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
@@ -2680,7 +2803,6 @@
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
- "dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -3139,8 +3261,7 @@
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/yn": {
"version": "3.1.1",
diff --git a/package.json b/package.json
index c1c88b9..b03d3b4 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,9 @@
"author": "",
"license": "ISC",
"devDependencies": {
+ "@types/bcryptjs": "^2.4.5",
"@types/express": "^4.17.18",
+ "@types/jsonwebtoken": "^9.0.4",
"@types/lodash.clonedeep": "^4.5.7",
"@types/mongoose": "^5.11.97",
"@types/node": "^20.8.10",
@@ -25,8 +27,11 @@
"typescript": "^5.2.2"
},
"dependencies": {
+ "bcryptjs": "^2.4.3",
+ "dotenv": "^16.3.1",
"express": "^4.18.2",
"joi": "^17.11.0",
+ "jsonwebtoken": "^9.0.2",
"lodash.clonedeep": "^4.5.0",
"mongodb": "^6.2.0",
"mongoose": "^7.6.4",
diff --git a/src/config/database.ts b/src/config/database.ts
new file mode 100644
index 0000000..da5998a
--- /dev/null
+++ b/src/config/database.ts
@@ -0,0 +1,29 @@
+import { connect, disconnect } from "mongoose";
+
+export const connectDB = async () => {
+ const { MONGO_URI } = process.env;
+ console.log(MONGO_URI)
+
+ if (!MONGO_URI) {
+ console.log("Please provide DataBase URI to connect. exiting now...");
+ process.exit(1);
+ }
+
+ try {
+ await connect(MONGO_URI);
+ console.log("Successfully connected to database");
+
+ // listen (ctrl-c)
+ process.on("SIGINT", async () => {
+ await disconnect();
+ console.log("DataBase is disconnected");
+ process.exit();
+ });
+
+ }
+ catch (err) {
+ console.log("DataBase connection failed. exiting now...");
+ console.error(err);
+ process.exit(1);
+ }
+}
diff --git a/src/controller/cart.controller.ts b/src/controller/cart.controller.ts
index a6bd7b7..0a0df00 100644
--- a/src/controller/cart.controller.ts
+++ b/src/controller/cart.controller.ts
@@ -1,6 +1,8 @@
import express, { NextFunction, Request, Response } from "express";
-import { createOrGetCart, createOrder, deleteCart, updateCart } from "../service/cart.service";
import Joi from "joi";
+import { isAdmin } from "../middleware/isAdmin";
+import { userHeaderHandler } from "../middleware/auth";
+import { createOrGetCart, createOrder, deleteCart, updateCart } from "../service/cart.service";
import { getPutSchemeError } from "../utils/errors";
const cartRouter = express.Router();
@@ -23,7 +25,7 @@ const putValidator = async (req: Request, _: Response, next: NextFunction) => {
cartRouter.get("/", async (req: Request, res: Response, next: NextFunction) => {
try {
- const userId = req.header('x-user-id')!;
+ const userId = req.user.user_id;
const cart = await createOrGetCart(userId);
res.send(cart);
}
@@ -32,7 +34,7 @@ cartRouter.get("/", async (req: Request, res: Response, next: NextFunction) => {
}
});
-cartRouter.delete("/", async (req: Request, res: Response, next: NextFunction) => {
+cartRouter.delete("/", isAdmin, userHeaderHandler, async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.header('x-user-id')!;
const data = await deleteCart(userId);
@@ -45,7 +47,7 @@ cartRouter.delete("/", async (req: Request, res: Response, next: NextFunction) =
cartRouter.post("/checkout", async (req: Request, res: Response, next: NextFunction) => {
try {
- const userId = req.header('x-user-id')!;
+ const userId = req.user.user_id;
const order = await createOrder(userId, req.body);
res.send(order);
}
@@ -56,7 +58,7 @@ cartRouter.post("/checkout", async (req: Request, res: Response, next: NextFunct
cartRouter.put("/", putValidator, async (req: Request, res: Response, next: NextFunction) => {
try {
- const userId = req.header('x-user-id')!;
+ const userId = req.user.user_id;
const order = await updateCart(userId, req.body);
res.send(order);
}
diff --git a/src/controller/user.controller.ts b/src/controller/user.controller.ts
new file mode 100644
index 0000000..911f344
--- /dev/null
+++ b/src/controller/user.controller.ts
@@ -0,0 +1,64 @@
+import express, { NextFunction, Request, Response } from "express";
+import Joi from "joi";
+import { createUser, loginUser } from "../service/user.service";
+import { getPutSchemeError } from "../utils/errors";
+
+const registerSchema = Joi.object({
+ name: Joi.string().required(),
+ isAdmin: Joi.boolean().required(),
+ email: Joi.string().email().required(),
+ password: Joi.string().length(10).required(),
+})
+
+const loginSchema = Joi.object({
+ email: Joi.string().email().required(),
+ password: Joi.string().length(10).required(),
+})
+
+const registerValidator = async (req: Request, _: Response, next: NextFunction) => {
+ try {
+ await registerSchema.validateAsync(req.body);
+ next();
+ }
+ catch (err) {
+ next(getPutSchemeError());
+ }
+}
+
+const loginValidator = async (req: Request, _: Response, next: NextFunction) => {
+ try {
+ await loginSchema.validateAsync(req.body);
+ next();
+ }
+ catch (err) {
+ next(getPutSchemeError());
+ }
+}
+
+const userRouter = express.Router();
+userRouter.use(express.json());
+
+userRouter.post("/register", registerValidator, async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { name, isAdmin, email, password } = req.body;
+ await createUser(name, isAdmin, email, password);
+ res.status(201).send('User successfully registered');
+ }
+ catch (err) {
+ next(err);
+ }
+});
+
+userRouter.post("/login", loginValidator, async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const { email, password } = req.body;
+ const token = await loginUser(email, password);
+ res.send(token)
+ }
+ catch (err) {
+ next(err);
+ }
+});
+
+
+export default userRouter;
diff --git a/src/db/cart.db.js b/src/db/cart.db.js
deleted file mode 100644
index 263d7f4..0000000
--- a/src/db/cart.db.js
+++ /dev/null
@@ -1 +0,0 @@
-export const carts = [];
diff --git a/src/db/db.js b/src/db/db.js
index 9f039a3..17ef717 100644
--- a/src/db/db.js
+++ b/src/db/db.js
@@ -1,9 +1,2 @@
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/db/order.db.js b/src/db/order.db.js
deleted file mode 100644
index abdacfb..0000000
--- a/src/db/order.db.js
+++ /dev/null
@@ -1 +0,0 @@
-export const orders=[];
diff --git a/src/db/user.db.js b/src/db/user.db.js
deleted file mode 100644
index 558bc33..0000000
--- a/src/db/user.db.js
+++ /dev/null
@@ -1,6 +0,0 @@
-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 06157ba..52eead3 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,79 +1,52 @@
-import express, { NextFunction, Request, Response } from "express";
+import express from "express";
+import { config } from "dotenv";
+import { connectDB } from "./config/database.ts";
import cartRouter from "./controller/cart.controller.ts";
import productRouter from "./controller/product.controller.ts";
-import { ResponseError, getForbidenError, getUnauthorizedError } from "./utils/errors.ts";
-import { getResponseError } from "./utils/utils.ts";
-import { connect, disconnect } from "mongoose";
-import { UserModel } from "./scheme/UserScheme.ts";
+import userRouter from "./controller/user.controller.ts";
+import { CurrentUser, verifyToken } from "./middleware/auth.ts";
+import { errorHandler } from "./middleware/error.ts";
//for seeders
-// import { runUserSeeder } from "./seeder/userSeeder.ts";
// import { ProductModel } from "./scheme/ProductScheme.ts";
// import { runProductSeeder } from "./seeder/productSeeder.ts";
-//for cart check
-// import { CartModel } from "./scheme/CartScheme.ts";
-export const app = express();
-const PORT = 3000;
-
-const headerHandler = async (req: Request, _: Response, next: NextFunction) => {
- try {
- const userId = req.header('x-user-id') ?? '';
- if (!userId) {
- next(getForbidenError());
- }
- const user = await UserModel.findById(userId);
- if (!user) {
- next(getUnauthorizedError());
+declare global {
+ // eslint-disable-next-line @typescript-eslint/no-namespace
+ namespace Express {
+ interface Request {
+ user: CurrentUser
}
- next();
- }
- catch (err) {
- next(err);
- }
-}
-
-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.use('/api/products', headerHandler, productRouter, errorHandler);
-
export const init = (async () => {
try {
- await connect("mongodb://127.0.0.1:27017/NM_SHOP");
+ config();
+ const { API_PORT } = process.env;
+ const PORT = API_PORT || 3000;
+
+ await connectDB();
+ const app = express();
+
+ app.use('/api', verifyToken);
+ app.use('/api/profile/cart', cartRouter);
+ app.use('/api/products', productRouter);
+ app.use('/user', userRouter);
+ app.use(errorHandler);
+
+
app.listen(PORT, () => {
console.log(`Server is Fire at http://localhost:${PORT}`);
});
- //mock data for first run
- // const user = await UserModel.findOne();
- // if (!user) {
- // await runUserSeeder();
- // }
+
+ //mock product data for first run
// const product = await ProductModel.findOne();
// if (!product) {
// await runProductSeeder();
// }
- //check cart
- // const doc = new CartModel({items: [{ count: 5 }]});
- // console.log('doc', doc);
- // console.log(doc.items[0].ownerDocument()===doc);
}
catch (err) {
console.log(err);
}
})();
-
-// listen (ctrl-c)
-process.on("SIGINT", async () => {
- await disconnect();
- console.log("Server is disconnected");
- process.exit();
-});
diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts
new file mode 100644
index 0000000..0880cbe
--- /dev/null
+++ b/src/middleware/auth.ts
@@ -0,0 +1,48 @@
+import { NextFunction, Request, Response } from "express";
+import * as jwt from "jsonwebtoken";
+import { UserModel } from "../scheme/UserScheme";
+import { getAuthError, getTokenError, getUnauthorizedError } from "../utils/errors";
+
+export interface CurrentUser {
+ user_id: string
+ email: string,
+ role: string
+}
+
+export const verifyToken = async (req: Request, res: Response, next: NextFunction) => {
+ try {
+ const authHeader = req.headers.authorization;
+ if (!authHeader) {
+ next(getAuthError());
+ }
+ const [tokenType, token] = authHeader!.split(" ");
+ if (tokenType !== "Bearer") {
+ next(getTokenError(403));
+ }
+ else {
+ const user = jwt.verify(token, process.env.TOKEN_KEY!) as CurrentUser;
+ req.user = user;
+ next();
+ }
+ }
+ catch (err) {
+ next(getTokenError());
+ }
+}
+
+export const userHeaderHandler = async (req: Request, _: Response, next: NextFunction) => {
+ try {
+ const userId = req.header('x-user-id') ?? '';
+ if (!userId) {
+ next(getUnauthorizedError());
+ }
+ const user = await UserModel.findById(userId);
+ if (!user) {
+ next(getUnauthorizedError());
+ }
+ next();
+ }
+ catch (err) {
+ next(err);
+ }
+}
diff --git a/src/middleware/error.ts b/src/middleware/error.ts
new file mode 100644
index 0000000..380d76e
--- /dev/null
+++ b/src/middleware/error.ts
@@ -0,0 +1,13 @@
+import { NextFunction, Request, Response } from "express";
+import { ResponseError } from "../utils/errors";
+import { getResponseError } from "../utils/utils";
+
+export 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));
+}
diff --git a/src/middleware/isAdmin.ts b/src/middleware/isAdmin.ts
new file mode 100644
index 0000000..b0d317a
--- /dev/null
+++ b/src/middleware/isAdmin.ts
@@ -0,0 +1,12 @@
+import { NextFunction, Request, Response } from "express";
+import { getAdminError } from "../utils/errors";
+
+export const isAdmin = async (req: Request, res: Response, next: NextFunction) => {
+ const currentUser = req.user;
+ if (currentUser.role !== 'admin') {
+ next(getAdminError());
+ }
+ else {
+ next();
+ }
+}
diff --git a/src/repository/cart.repository.ts b/src/repository/cart.repository.ts
deleted file mode 100644
index 4dafc3c..0000000
--- a/src/repository/cart.repository.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import { cartDB } from "../db/db";
-import { IPopulatedCartItem } from "../scheme/CartScheme";
-import { getUUID } from "../utils/utils";
-
-interface ICart {
- id: string; // uuid
- userId: string;
- isDeleted: boolean;
- items: IPopulatedCartItem[];
-}
-
-enum CartEditableProperties {
- Items = 'items',
- IsDeleted = "isDeleted"
-}
-
-export const generateCart = (userId: string): ICart => {
- const newCart: ICart = {
- id: getUUID(),
- userId: userId,
- isDeleted: false,
- items: [],
- }
- return newCart;
-}
-
-export const pushCart = async (cart: ICart) => {
- await cartDB.push(cart);
-}
-
-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 | IPopulatedCartItem[]) => {
- cartDB[cartIdx][property] = value;
-}
-
-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: IPopulatedCartItem) => item.product.product === productId);
- return index;
-}
-
-export const pushCartItem = async (cartIdx: number, product: IPopulatedCartItem) => {
- 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/repository/order.repository.ts b/src/repository/order.repository.ts
deleted file mode 100644
index 950dc29..0000000
--- a/src/repository/order.repository.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { orderDB } from "../db/db";
-import { IPopulatedCartItem } from "../scheme/CartScheme";
-
-type ORDER_STATUS = 'created' | 'completed';
-
-interface IPopulatedOrder {
- id: string,
- userId: string;
- cartId: string;
- items: IPopulatedCartItem[]
- payment: {
- type: string,
- address?: unknown,
- creditCard?: unknown,
- },
- delivery: {
- type: string,
- address: unknown,
- },
- comments: string,
- status: ORDER_STATUS;
- total: number;
-}
-
-export const pushOrder = async (order: IPopulatedOrder): Promise => {
- await orderDB.push(order);
- return orderDB.length - 1;
-}
-
-export const getOrderByIdx = async (idx: number): Promise => {
- return await orderDB[idx];
-}
diff --git a/src/repository/product.repository.ts b/src/repository/product.repository.ts
deleted file mode 100644
index 32ff596..0000000
--- a/src/repository/product.repository.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import { productDB } from "../db/db";
-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
deleted file mode 100644
index 6651535..0000000
--- a/src/repository/user.repository.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { userDB } from "../db/db";
-
-export const findUser = async (userId: string) => {
- return await userDB.find((id) => id === userId);
-}
diff --git a/src/scheme/UserScheme.ts b/src/scheme/UserScheme.ts
index d3b8c38..34bdb33 100644
--- a/src/scheme/UserScheme.ts
+++ b/src/scheme/UserScheme.ts
@@ -2,10 +2,16 @@ import { Schema, model } from "mongoose";
export interface IUser {
name: string,
+ email: string,
+ password: string,
+ role: string
}
const userSchema = new Schema({
name: { type: String, required: true },
+ email: { type: String, unique: true, required: true },
+ password: { type: String, required: true },
+ role: { type: String, required: true },
}, { versionKey: false })
export const UserModel = model("User", userSchema);
diff --git a/src/seeder/userSeeder.ts b/src/seeder/userSeeder.ts
deleted file mode 100644
index a8c3548..0000000
--- a/src/seeder/userSeeder.ts
+++ /dev/null
@@ -1,9 +0,0 @@
-import { userDB } from "../db/db"
-import { UserModel } from "../scheme/UserScheme";
-
-export const runUserSeeder = async () => {
- userDB.forEach(async (value, i) => {
- const newUser = new UserModel({ name: `User-${i}` });
- await newUser.save();
- });
-}
diff --git a/src/service/user.service.ts b/src/service/user.service.ts
new file mode 100644
index 0000000..c61728d
--- /dev/null
+++ b/src/service/user.service.ts
@@ -0,0 +1,39 @@
+import bcrypt from "bcryptjs";
+import * as jwt from "jsonwebtoken";
+import { UserModel } from "../scheme/UserScheme";
+import { throwLoginUserError, throwRegisterError } from "../utils/errors";
+
+const findUserOrFail = async (email: string) => {
+ return UserModel.findOne({ email }).orFail(() => throwLoginUserError());
+}
+
+export const createUser = async (name: string, isAdmin: boolean, email: string, password: string) => {
+ const oldUser = await UserModel.findOne({ email });
+ if (oldUser) {
+ throwRegisterError();
+ }
+ const encryptedPassword = await bcrypt.hash(password, 10);
+ await UserModel.create({
+ name,
+ email: email.toLowerCase(),
+ password: encryptedPassword,
+ role: isAdmin ? "admin" : "customer"
+ });
+}
+
+export const loginUser = async (email: string, password: string) => {
+ const user = await findUserOrFail(email);
+ if (!isTheSameCrypt(password, user.password)) {
+ throwLoginUserError()
+ }
+ const token = jwt.sign(
+ { user_id: user._id.toString(), email, role: user.role },
+ process.env.TOKEN_KEY!,
+ { expiresIn: '2h' }
+ );
+ return { token };
+}
+
+const isTheSameCrypt = async (str1: string, str2: string) => {
+ await bcrypt.compare(str1, str2)
+}
diff --git a/src/utils/cartUtils.ts b/src/utils/cartUtils.ts
index d66e008..e8c456f 100644
--- a/src/utils/cartUtils.ts
+++ b/src/utils/cartUtils.ts
@@ -1,4 +1,4 @@
-import { IPopulatedCartItem, ICartResponse, ICart, IDeleteCartResponse, OmitCart, PopulatedCart } from "../scheme/CartScheme";
+import { IPopulatedCartItem, ICartResponse, ICart, IDeleteCartResponse, OmitCart, PopulatedCart } from "../scheme/CartScheme";
import { IOrderInfo } from "../scheme/OrderScheme";
export const getOmitCart = ({ _id, items }: PopulatedCart | ICart): OmitCart => {
diff --git a/src/utils/errors.ts b/src/utils/errors.ts
index 9832f11..207a01c 100644
--- a/src/utils/errors.ts
+++ b/src/utils/errors.ts
@@ -15,26 +15,10 @@ const getResposeError = (message: string, status: number): ResponseError => {
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 throwNoProductExists = () => {
throw new ResponseError(`No product for user`, 400);
}
@@ -43,18 +27,30 @@ 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)
-}
-
export const getPutSchemeError = (status = 400) => {
return getResposeError(`Bad request body`, status)
}
+
+export const throwRegisterError = (status = 409) => {
+ throw getResposeError(`User Already Exist. Please Login`, status)
+}
+
+export const throwLoginUserError = (status = 400) => {
+ throw getResposeError(`Invalid Credentials`, status)
+}
+
+export const getAuthError = (status = 401) => {
+ return getResposeError(`Token is required`, status);
+}
+
+export const getTokenError = (status = 401) => {
+ return getResposeError(`Invalid Token`, status);
+}
+
+export const getAdminError = (status = 403) => {
+ return getResposeError(`Forbidden`, status);
+}
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index 5662690..0c76711 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -1,24 +1,5 @@
-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,
diff --git a/swagger.md b/swagger.md
index 6e1231d..ad11ff4 100644
--- a/swagger.md
+++ b/swagger.md
@@ -1,14 +1,12 @@
---
-title: API v1.0.0
+title: shop api v1.0.0
language_tabs:
- - http: http
- - HTTP: HTTP
+ - "'http": HTTP'
language_clients:
- - http: ""
- - HTTP: ""
+ - "'http": ""
toc_footers: []
includes: []
-search: false
+search: true
highlight_theme: darkula
headingLevel: 2
@@ -16,42 +14,24 @@ headingLevel: 2
-null v1.0.0
+shop 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
+# Authentication
-## get__api_profile_cart
-
-> Code samples
+- HTTP Authentication, scheme: bearer
-```http
-GET /api/profile/cart HTTP/1.1
+cart
-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
-```
+> Code samples
`GET /api/profile/cart`
*Get user cart or create if it is missing*
-Parameters
-
-|Name|In|Type|Required|Description|
-|---|---|---|---|---|
-|x-user-id|header|string(uuid)|true|User id (uuid)|
-
> Example responses
> 200 Response
@@ -60,11 +40,11 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": {
"cart": {
- "id": "eb5a26af-6e4c-4f31-a9b1-3450d42ac66c",
+ "id": "6546650c159ab6e8bb59f925",
"items": [
{
"product": {
- "id": "891389f0-4312-42d6-a650-6fda0959c734",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -85,7 +65,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "User is not authorized"
+ "message": "Token is required"
}
}
```
@@ -96,7 +76,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "You must be authorized user"
+ "message": "Invalid Token"
}
}
```
@@ -117,36 +97,19 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|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)|
+|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no token is found)|[CartResponse](#schemacartresponse)|
+|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when token is missed or invalid)|[CartResponse](#schemacartresponse)|
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)|
-
-This operation does not require authentication
+
+To perform this operation, you must be authenticated by means of one of the following methods:
+bearerAuth
## 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*
@@ -155,7 +118,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
```json
{
- "productId": "915b2f40-9fd9-47f2-9b51-628f3dc69aac",
+ "productId": "65426846103aa5e276433519",
"count": 5
}
```
@@ -164,7 +127,6 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|Name|In|Type|Required|Description|
|---|---|---|---|---|
-|x-user-id|header|string(uuid)|true|User id (uuid)|
|body|body|[UpdateCartRequestBody](#schemaupdatecartrequestbody)|false|none|
> Example responses
@@ -175,11 +137,11 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": {
"cart": {
- "id": "eb5a26af-6e4c-4f31-a9b1-3450d42ac66c",
+ "id": "6546650c159ab6e8bb59f925",
"items": [
{
"product": {
- "id": "891389f0-4312-42d6-a650-6fda0959c734",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -211,7 +173,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "User is not authorized"
+ "message": "Token is required"
}
}
```
@@ -222,7 +184,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "You must be authorized user"
+ "message": "Invalid Token"
}
}
```
@@ -253,37 +215,22 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|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)|
+|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.|[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)|
+|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no token is found)|[CartResponse](#schemacartresponse)|
+|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when token is missed or invalid)|[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)|
-
-This operation does not require authentication
+
+To perform this operation, you must be authenticated by means of one of the following methods:
+bearerAuth
## 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`
*Empty user cart*
@@ -292,7 +239,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|Name|In|Type|Required|Description|
|---|---|---|---|---|
-|x-user-id|header|string(uuid)|true|User id (uuid)|
+|x-user-id|header|string|true|User id|
> Example responses
@@ -313,7 +260,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "User is not authorized"
+ "message": "Token is required"
}
}
```
@@ -324,7 +271,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "You must be authorized user"
+ "message": "Forbidden"
}
}
```
@@ -345,44 +292,23 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|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)|
+|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no token is found or Header x-user-id is missing or no user with such id)|[CartResponse](#schemacartresponse)|
+|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when user isn't admin)|[CartResponse](#schemacartresponse)|
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)|
-
-This operation does not require authentication
+
+To perform this operation, you must be authenticated by means of one of the following methods:
+bearerAuth
## 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`
*Create an order*
-Parameters
-
-|Name|In|Type|Required|Description|
-|---|---|---|---|---|
-|x-user-id|header|string(uuid)|true|User id (uuid)|
-
> Example responses
> 200 Response
@@ -391,13 +317,13 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": {
"order": {
- "id": "6c36d6fa-f694-4f9c-9b2c-6f7049d38f4a",
- "userId": "6dc52b3c-de7e-431a-84b8-0ec56e0774d4",
- "cartId": "cadff0c5-0079-4db8-b6bf-84c9c2633ca3",
+ "id": "654664c3159ab6e8bb59f912",
+ "userId": "654660aed551a02ee536fe4c",
+ "cartId": "65466369efd3f9a3ddf5f223",
"items": [
{
"product": {
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -406,7 +332,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
},
{
"product": {
- "id": "afdd68c4-d359-45e6-b9fd-c8fdb2a162a0",
+ "id": "65426846103aa5e276433518",
"title": "Pen",
"description": "Cute pen",
"price": 20
@@ -449,7 +375,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "User is not authorized"
+ "message": "Token is required"
}
}
```
@@ -460,7 +386,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "You must be authorized user"
+ "message": "Invalid Token"
}
}
```
@@ -482,46 +408,25 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|---|---|---|---|
|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)|
+|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no token is found)|[CartResponse](#schemacartresponse)|
+|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when token is missed or invalid)|[CartResponse](#schemacartresponse)|
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)|
-
-This operation does not require authentication
+
+To perform this operation, you must be authenticated by means of one of the following methods:
+bearerAuth
-product
+product
## 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`
*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
@@ -530,13 +435,13 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": [
{
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
},
{
- "id": "afdd68c4-d359-45e6-b9fd-c8fdb2a162a0",
+ "id": "a65426846103aa5e276433518",
"title": "Pen",
"description": "Cute pen",
"price": 20
@@ -552,7 +457,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "User is not authorized"
+ "message": "Token is required"
}
}
```
@@ -563,7 +468,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "You must be authorized user"
+ "message": "Invalid Token"
}
}
```
@@ -584,8 +489,8 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|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)|
+|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no token is found)|[CartResponse](#schemacartresponse)|
+|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when token is missed or invalid)|[CartResponse](#schemacartresponse)|
|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)|
Response Schema
@@ -603,30 +508,15 @@ Status Code **200**
|» error|[ErrorResponse](#schemaerrorresponse)¦null|false|none|none|
|»» message|string|false|none|none|
-
-This operation does not require authentication
+
+To perform this operation, you must be authenticated by means of one of the following methods:
+bearerAuth
## 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}`
*Returns single product*
@@ -635,8 +525,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|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)|
+|productId|path|number|true|Id of product to return|
> Example responses
@@ -645,7 +534,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
```json
{
"data": {
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -660,7 +549,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "User is not authorized"
+ "message": "Token is required"
}
}
```
@@ -671,7 +560,7 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
{
"data": null,
"error": {
- "message": "You must be authorized user"
+ "message": "Invalid Token"
}
}
```
@@ -703,11 +592,202 @@ x-user-id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
|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)|
+|401|[Unauthorized](https://tools.ietf.org/html/rfc7235#section-3.1)|Unauthorized (when no token is found)|[CartResponse](#schemacartresponse)|
+|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when token is missed or invalid)|[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)|
+
+To perform this operation, you must be authenticated by means of one of the following methods:
+bearerAuth
+
+
+user
+
+## post__login
+
+> Code samples
+
+`POST /login`
+
+*Login user*
+
+> Body parameter
+
+```json
+{
+ "email": "test2@gmail.com",
+ "password": 123456789
+}
+```
+
+Parameters
+
+|Name|In|Type|Required|Description|
+|---|---|---|---|---|
+|body|body|[LoginUserRequestBody](#schemaloginuserrequestbody)|false|none|
+
+> Example responses
+
+> 200 Response
+
+```json
+{
+ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNjU0NjYwYWVkNTUxYTAyZWU1MzZmZTRjIiwiZW1haWwiOiJ0ZXN0MkBnbWFpbC5jb20iLCJyb2xlIjoiY3VzdG9tZXIiLCJpYXQiOjE2OTkxMTE1NTUsImV4cCI6MTY5OTExODc1NX0.2UkbA0U464r2WfKN6RMWvhMzc3wVo71dq6rKvUmoVTs"
+}
+```
+
+> 400 Response
+
+```json
+{
+ "data": null,
+ "error": {
+ "message": "Cart is empty"
+ }
+}
+```
+
+> 401 Response
+
+```json
+{
+ "data": null,
+ "error": {
+ "message": "Token is required"
+ }
+}
+```
+
+> 403 Response
+
+```json
+{
+ "data": null,
+ "error": {
+ "message": "Invalid Token"
+ }
+}
+```
+
+> 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)|User is logined|Inline|
+|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 token is found)|[CartResponse](#schemacartresponse)|
+|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when token is missed or invalid)|[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|
+|---|---|---|---|---|
+|» token|string|false|none|none|
+
+
+This operation does not require authentication
+
+
+## post__register
+
+> Code samples
+
+`POST /register`
+
+*Register user*
+
+> Body parameter
+
+```json
+{
+ "email": "test2@gmail.com",
+ "password": 123456789,
+ "name": "Name",
+ "isAdmin": true
+}
+```
+
+Parameters
+
+|Name|In|Type|Required|Description|
+|---|---|---|---|---|
+|body|body|[RegisterUserRequestBody](#schemaregisteruserrequestbody)|false|none|
+
+> Example responses
+
+> 200 Response
+
+> 400 Response
+
+```json
+{
+ "data": null,
+ "error": {
+ "message": "Cart is empty"
+ }
+}
+```
+
+> 401 Response
+
+```json
+{
+ "data": null,
+ "error": {
+ "message": "Token is required"
+ }
+}
+```
+
+> 403 Response
+
+```json
+{
+ "data": null,
+ "error": {
+ "message": "Invalid Token"
+ }
+}
+```
+
+> 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)|User is registered|None|
+|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 token is found)|[CartResponse](#schemacartresponse)|
+|403|[Forbidden](https://tools.ietf.org/html/rfc7231#section-6.5.3)|Forbidden (when token is missed or invalid)|[CartResponse](#schemacartresponse)|
+|500|[Internal Server Error](https://tools.ietf.org/html/rfc7231#section-6.6.1)|Internal server error|[CartResponse](#schemacartresponse)|
+
+Response Schema
+
This operation does not require authentication
@@ -723,11 +803,11 @@ This operation does not require authentication
```json
{
- "id": "dd5ec5ab-deaa-419c-8a6b-7e67b1f7ec87",
+ "id": "654660f1d551a02ee536fe53",
"items": [
{
"product": {
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -743,7 +823,7 @@ This operation does not require authentication
|Name|Type|Required|Restrictions|Description|
|---|---|---|---|---|
-|id|string|false|none|Cart id (uuid)|
+|id|string|false|none|Cart id|
|items|[[CartItem](#schemacartitem)]|false|none|Items added to cart|
CartItem
@@ -756,7 +836,7 @@ This operation does not require authentication
```json
{
"product": {
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -782,7 +862,7 @@ This operation does not require authentication
```json
{
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -814,7 +894,7 @@ This operation does not require authentication
"items": [
{
"product": {
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -868,11 +948,11 @@ This operation does not require authentication
{
"data": {
"cart": {
- "id": "dd5ec5ab-deaa-419c-8a6b-7e67b1f7ec87",
+ "id": "654660f1d551a02ee536fe53",
"items": [
{
"product": {
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -916,7 +996,7 @@ This operation does not require authentication
"items": [
{
"product": {
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -963,7 +1043,7 @@ This operation does not require authentication
```json
{
"data": {
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -993,7 +1073,7 @@ This operation does not require authentication
{
"data": [
{
- "id": "5c293ad0-19d0-41ee-baa3-4c648f9f7697",
+ "id": "65426846103aa5e276433519",
"title": "Book",
"description": "Interesting book",
"price": 200
@@ -1022,7 +1102,7 @@ This operation does not require authentication
```json
{
- "productId": "915b2f40-9fd9-47f2-9b51-628f3dc69aac",
+ "productId": "65426846103aa5e276433519",
"count": 5
}
@@ -1057,6 +1137,54 @@ This operation does not require authentication
|email|string|false|none|none|
|password|string|false|none|none|
+LoginUserRequestBody
+
+
+
+
+
+
+```json
+{
+ "email": "test2@gmail.com",
+ "password": 123456789
+}
+
+```
+
+### Properties
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|email|string|false|none|none|
+|password|string|false|none|none|
+
+RegisterUserRequestBody
+
+
+
+
+
+
+```json
+{
+ "email": "test2@gmail.com",
+ "password": 123456789,
+ "name": "Name",
+ "isAdmin": true
+}
+
+```
+
+### Properties
+
+|Name|Type|Required|Restrictions|Description|
+|---|---|---|---|---|
+|email|string|false|none|none|
+|password|string|false|none|none|
+|name|string|false|none|none|
+|isAdmin|boolean|false|none|none|
+
EmptySuccessResponse
diff --git a/swagger.yaml b/swagger.yaml
index d0906d7..fc0f98f 100644
--- a/swagger.yaml
+++ b/swagger.yaml
@@ -1,25 +1,137 @@
openapi: 3.0.3
info:
- title:
+ title: "shop api"
version: 1.0.0
tags:
- name: cart
- name: product
+ - name: user
paths:
+ /login:
+ post:
+ tags:
+ - user
+ summary: Login user
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/LoginUserRequestBody'
+ responses:
+ '200':
+ description: User is logined
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ token:
+ type: string
+ example:
+ token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiNjU0NjYwYWVkNTUxYTAyZWU1MzZmZTRjIiwiZW1haWwiOiJ0ZXN0MkBnbWFpbC5jb20iLCJyb2xlIjoiY3VzdG9tZXIiLCJpYXQiOjE2OTkxMTE1NTUsImV4cCI6MTY5OTExODc1NX0.2UkbA0U464r2WfKN6RMWvhMzc3wVo71dq6rKvUmoVTs
+ '403':
+ description: Forbidden (when token is missed or invalid)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CartResponse'
+ example:
+ data: null
+ error:
+ message: Invalid Token
+ '401':
+ description: Unauthorized (when no token is found)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CartResponse'
+ example:
+ data: null
+ error:
+ message: Token is required
+ '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'
+ /register:
+ post:
+ tags:
+ - user
+ summary: Register user
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/RegisterUserRequestBody'
+ responses:
+ '200':
+ description: User is registered
+ content:
+ text:
+ example: User successfully registered
+ '403':
+ description: Forbidden (when token is missed or invalid)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CartResponse'
+ example:
+ data: null
+ error:
+ message: Invalid Token
+ '401':
+ description: Unauthorized (when no token is found)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CartResponse'
+ example:
+ data: null
+ error:
+ message: Token is required
+ '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/profile/cart:
get:
+ security:
+ - bearerAuth: []
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
@@ -30,10 +142,10 @@ paths:
example:
data:
cart:
- id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
+ id: 6546650c159ab6e8bb59f925
items:
- product:
- id: 891389f0-4312-42d6-a650-6fda0959c734
+ id: 65426846103aa5e276433519
title: Book
description: Interesting book
price: 200
@@ -41,7 +153,7 @@ paths:
total: 400
error: null
'403':
- description: Forbidden (when authorization header is missing)
+ description: Forbidden (when token is missed or invalid)
content:
application/json:
schema:
@@ -49,9 +161,9 @@ paths:
example:
data: null
error:
- message: You must be authorized user
+ message: Invalid Token
'401':
- description: Unauthorized (when no user matching authorization header is found)
+ description: Unauthorized (when no token is found)
content:
application/json:
schema:
@@ -59,7 +171,7 @@ paths:
example:
data: null
error:
- message: User is not authorized
+ message: Token is required
'500':
description: Internal server error
content:
@@ -71,18 +183,11 @@ paths:
error:
message: 'Internal Server error'
put:
+ security:
+ - bearerAuth: []
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:
@@ -90,7 +195,7 @@ paths:
$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.
+ 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.
content:
application/json:
schema:
@@ -98,10 +203,10 @@ paths:
example:
data:
cart:
- id: eb5a26af-6e4c-4f31-a9b1-3450d42ac66c
+ id: 6546650c159ab6e8bb59f925
items:
- product:
- id: 891389f0-4312-42d6-a650-6fda0959c734
+ id: 65426846103aa5e276433519
title: Book
description: Interesting book
price: 200
@@ -119,7 +224,7 @@ paths:
error:
message: Products are not valid
'403':
- description: Forbidden (when authorization header is missing)
+ description: Forbidden (when token is missed or invalid)
content:
application/json:
schema:
@@ -127,9 +232,9 @@ paths:
example:
data: null
error:
- message: You must be authorized user
+ message: Invalid Token
'401':
- description: Unauthorized (when no user matching authorization header is found)
+ description: Unauthorized (when no token is found)
content:
application/json:
schema:
@@ -137,7 +242,7 @@ paths:
example:
data: null
error:
- message: User is not authorized
+ message: Token is required
'404':
description: Not Found
content:
@@ -159,6 +264,8 @@ paths:
error:
message: 'Internal Server error'
delete:
+ security:
+ - bearerAuth: []
tags:
- cart
summary: Empty user cart
@@ -167,10 +274,9 @@ paths:
name: x-user-id
schema:
type: string
- format: uuid
required: true
- description: User id (uuid)
- example: 'eb5a26af-6e4c-4f31-a9b1-3450d42ac66c'
+ description: User id
+ example: '654660aed551a02ee536fe4c'
responses:
'200':
description: Returns success = true if cart was successfully emptied
@@ -183,7 +289,7 @@ paths:
success: true
error: null
'403':
- description: Forbidden (when authorization header is missing)
+ description: Forbidden (when user isn't admin)
content:
application/json:
schema:
@@ -191,9 +297,9 @@ paths:
example:
data: null
error:
- message: You must be authorized user
+ message: Forbidden
'401':
- description: Unauthorized (when no user matching authorization header is found)
+ description: Unauthorized (when no token is found or Header x-user-id is missing or no user with such id)
content:
application/json:
schema:
@@ -201,7 +307,7 @@ paths:
example:
data: null
error:
- message: User is not authorized
+ message: Token is required
'500':
description: Internal server error
content:
@@ -214,18 +320,11 @@ paths:
message: 'Internal Server error'
/api/profile/cart/checkout:
post:
+ security:
+ - bearerAuth: []
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
@@ -236,18 +335,18 @@ paths:
example:
data:
order:
- id: 6c36d6fa-f694-4f9c-9b2c-6f7049d38f4a
- userId: 6dc52b3c-de7e-431a-84b8-0ec56e0774d4
- cartId: cadff0c5-0079-4db8-b6bf-84c9c2633ca3
+ id: 654664c3159ab6e8bb59f912
+ userId: 654660aed551a02ee536fe4c
+ cartId: 65466369efd3f9a3ddf5f223
items:
- product:
- id: 5c293ad0-19d0-41ee-baa3-4c648f9f7697
+ id: 65426846103aa5e276433519
title: Book
description: Interesting book
price: 200
count: 2
- product:
- id: afdd68c4-d359-45e6-b9fd-c8fdb2a162a0
+ id: 65426846103aa5e276433518
title: Pen
description: Cute pen
price: 20
@@ -264,7 +363,7 @@ paths:
total: 500
error: null
'403':
- description: Forbidden (when authorization header is missing)
+ description: Forbidden (when token is missed or invalid)
content:
application/json:
schema:
@@ -272,9 +371,9 @@ paths:
example:
data: null
error:
- message: You must be authorized user
+ message: Invalid Token
'401':
- description: Unauthorized (when no user matching authorization header is found)
+ description: Unauthorized (when no token is found)
content:
application/json:
schema:
@@ -282,7 +381,7 @@ paths:
example:
data: null
error:
- message: User is not authorized
+ message: Token is required
'400':
description: Bad request
content:
@@ -305,18 +404,11 @@ paths:
message: 'Internal Server error'
/api/products:
get:
+ security:
+ - bearerAuth: []
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
@@ -328,17 +420,17 @@ paths:
$ref: '#/components/schemas/ProductsResponse'
example:
data:
- - id: 5c293ad0-19d0-41ee-baa3-4c648f9f7697
+ - id: 65426846103aa5e276433519
title: Book
description: Interesting book
price: 200
- - id: afdd68c4-d359-45e6-b9fd-c8fdb2a162a0
+ - id: a65426846103aa5e276433518
title: Pen
description: Cute pen
price: 20
error: null
'403':
- description: Forbidden (when authorization header is missing)
+ description: Forbidden (when token is missed or invalid)
content:
application/json:
schema:
@@ -346,9 +438,9 @@ paths:
example:
data: null
error:
- message: You must be authorized user
+ message: Invalid Token
'401':
- description: Unauthorized (when no user matching authorization header is found)
+ description: Unauthorized (when no token is found)
content:
application/json:
schema:
@@ -356,7 +448,7 @@ paths:
example:
data: null
error:
- message: User is not authorized
+ message: Token is required
'500':
description: Internal server error
content:
@@ -369,24 +461,18 @@ paths:
message: 'Internal Server error'
/api/products/{productId}:
get:
+ security:
+ - bearerAuth: []
tags:
- product
summary: Returns single product
parameters:
- name: productId
in: path
- description: Id (uuid) of product to return
+ description: Id 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
@@ -396,13 +482,13 @@ paths:
$ref: '#/components/schemas/ProductResponse'
example:
data:
- id: 5c293ad0-19d0-41ee-baa3-4c648f9f7697
+ id: 65426846103aa5e276433519
title: Book
description: Interesting book
price: 200
error: null
'403':
- description: Forbidden (when authorization header is missing)
+ description: Forbidden (when token is missed or invalid)
content:
application/json:
schema:
@@ -410,9 +496,9 @@ paths:
example:
data: null
error:
- message: You must be authorized user
+ message: Invalid Token
'401':
- description: Unauthorized (when no user matching authorization header is found)
+ description: Unauthorized (when no token is found)
content:
application/json:
schema:
@@ -420,7 +506,7 @@ paths:
example:
data: null
error:
- message: User is not authorized
+ message: Token is required
'404':
description: Not found
content:
@@ -442,14 +528,19 @@ paths:
error:
message: 'Internal Server error'
components:
+ securitySchemes:
+ bearerAuth: # arbitrary name for the security scheme
+ type: http
+ scheme: bearer
+ bearerFormat: JWT
schemas:
Cart:
type: object
properties:
id:
type: string
- description: Cart id (uuid)
- example: dd5ec5ab-deaa-419c-8a6b-7e67b1f7ec87
+ description: Cart id
+ example: 654660f1d551a02ee536fe53
items:
type: array
description: Items added to cart
@@ -470,7 +561,7 @@ components:
id:
type: string
description: Product id (uuid)
- example: 5c293ad0-19d0-41ee-baa3-4c648f9f7697
+ example: 65426846103aa5e276433519
title:
type: string
description: Product name
@@ -561,7 +652,7 @@ components:
properties:
productId:
type: string
- example: "915b2f40-9fd9-47f2-9b51-628f3dc69aac"
+ example: "65426846103aa5e276433519"
count:
type: number
example: 5
@@ -574,6 +665,30 @@ components:
password:
type: string
example: "DDQldls?kdpw0fk"
+ LoginUserRequestBody:
+ type: object
+ properties:
+ email:
+ type: string
+ example: "test2@gmail.com"
+ password:
+ type: string
+ example: 0123456789
+ RegisterUserRequestBody:
+ type: object
+ properties:
+ email:
+ type: string
+ example: "test2@gmail.com"
+ password:
+ type: string
+ example: 0123456789
+ name:
+ type: string
+ example: Name
+ isAdmin:
+ type: boolean
+ example: true
EmptySuccessResponse:
type: object
properties: