diff --git a/.circleci/config.yml b/.circleci/config.yml index 5bd70a9..b7660e6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,9 +1,7 @@ version: 2.1 - orbs: node: circleci/node@4.0.0 - coveralls: coveralls/coveralls@2.1.0 executors: node-executor: @@ -13,26 +11,56 @@ executors: jobs: build: executor: node-executor + environment: + CC_TEST_REPORTER_ID: b996f145a438f80141cfcc86bb35a2c212a2a24c394abee18da6add05eaaee7e steps: - checkout - run: npm install + + - run: + name: Install Code Climate test reporter + command: | + curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + chmod +x ./cc-test-reporter + - run: + name: Initialize Code Climate test reporter + command: ./cc-test-reporter before-build - run: - name: Run Tests - command: npm test + name: Run Tests with Coverage + command: npx nyc npm run test:coverage + - run: + name: Format Coverage Report + command: npx nyc report --reporter=text-lcov > coverage.lcov + - run: + name: Upload Coverage Report to Code Climate + command : ./cc-test-reporter after-build -t lcov + when: always + - store_artifacts: # upload test coverage as artifact + path: ./coverage/lcov.info + deploy: executor: node-executor steps: + - checkout + - run: npm install + - run: + name: Install TypeScript + command: sudo npm install -g typescript - run: name: Build Project - command: tsc + + command: npx tsc + + - run: name: Compile TypeScript command: ./node_modules/.bin/tsc + workflows: version: 2 build_and_deploy: diff --git a/.github/workflows/job.yaml b/.github/workflows/job.yaml deleted file mode 100644 index 3818a70..0000000 --- a/.github/workflows/job.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: e-commerce-furebo-32-bn. - -on: - pull_request: - branches: - - main - push: - branches: - - main - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: '20' - - - name: Install dependencies - run: npm install - - - name: Run tests - run: npm test - - - name: Build app - run: npm run build - - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v4.0.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index be6a2df..0000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,30 +0,0 @@ - -name: Test Coverage - -on: [push, pull_request] - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Set up Node.js - uses: actions/setup-node@v2 - with: - node-version: '16' - - - name: Install dependencies - run: npm install - - - name: Run tests - run: npm run test - - - name: Upload coverage to CodeClimate - env: - CODECLIMATE_REPO_TOKEN: ${{ secrets.CODECLIMATE_REPO_TOKEN }} - run: | - npm install -g codeclimate-test-reporter - codeclimate-test-reporter < coverage/lcov.info \ No newline at end of file diff --git a/__test__/home.test.ts b/__test__/home.test.ts deleted file mode 100644 index bf176ff..0000000 --- a/__test__/home.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { expect, test } from "@jest/globals" -import request from "supertest" -import app from "../app" - - -describe("Testing home route", () => { - test("should return 200 status code", async() => { - const response = await request(app).get("/") - expect(response.body.message).toBe("welcome to ATLP Backend APIs"); - expect(response.statusCode).toBe(200) - }) -}) \ No newline at end of file diff --git a/app.ts b/app.ts index be27c34..050488f 100644 --- a/app.ts +++ b/app.ts @@ -2,12 +2,14 @@ import express, { Request, Response } from "express"; import userRoutes from "./src/routes/user.route"; import swaggerUi from 'swagger-ui-express'; import specs from './swagger.config'; +import morgan from "morgan"; const app = express(); app.use(express.json()); +app.use(morgan("dev")) -app.get('/', (_req: Request, res: Response) => { +app.get('/',(_req: Request, res: Response) => { return res.json({ message: "welcome to ATLP Backend APIs" }); }); app.use('/api/users', userRoutes); diff --git a/docker-compose.yml b/docker-compose.yml index 7be8984..d29cb72 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.8' +version: '3.9' services: postgresdb: @@ -27,5 +27,5 @@ services: - DB_NAME=${DB_NAME} - DB_PORT=${POSTGRESDB_LOCAL_PORT} -volumes: - db_data: \ No newline at end of file +volumes: + db_data: diff --git a/jest.config.js b/jest.config.js index d7d0810..a5c31c6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -9,7 +9,7 @@ module.exports = { resetMocks: true, restoreMocks: true, clearMocks: true, - testTimeout:30000, + testTimeout:60000, coverageReporters: ['html', 'text', 'lcov'], coverageDirectory: 'coverage', testPathIgnorePatterns: ['/node_modules/'] diff --git a/package-lock.json b/package-lock.json index ae2ecff..6424d4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "express": "^4.19.2", "joi": "^17.13.1", "jsonwebtoken": "^9.0.2", + "morgan": "^1.10.0", "nodemailer": "^6.9.13", "nodemon": "^3.1.0", "pg": "^8.11.5", @@ -2507,6 +2508,22 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/bcrypt": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", @@ -7230,6 +7247,45 @@ "node": "*" } }, + "node_modules/morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/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/morgan/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -7497,6 +7553,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", diff --git a/package.json b/package.json index 42df8b2..0e84175 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "express": "^4.19.2", "joi": "^17.13.1", "jsonwebtoken": "^9.0.2", + "morgan": "^1.10.0", "nodemailer": "^6.9.13", "nodemon": "^3.1.0", "pg": "^8.11.5", @@ -26,6 +27,7 @@ "dev": "nodemon server.ts", "start": "ts-node server.ts", "test": "jest --detectOpenHandles --coverage", + "test:coverage": "nyc jest --coverage", "build": "tsc", "migrate": "sequelize db:migrate" }, diff --git a/src/__test__/authMiddleware.test.ts b/src/__test__/authMiddleware.test.ts new file mode 100644 index 0000000..88dc390 --- /dev/null +++ b/src/__test__/authMiddleware.test.ts @@ -0,0 +1,95 @@ +import { Request, Response, NextFunction } from "express"; +import { protectRoute, restrictTo } from "../middlewares/auth.middleware"; +// import jwt from "jsonwebtoken"; + +type Headers = { + authorization?: string; +}; + +const mockRequest = (headers: Headers = {}) => { + return { + headers: { + authorization: headers.authorization || "", + }, + user: undefined, + } as Partial; +}; + +const mockResponse = () => { + const res = {} as Partial; + res.status = jest.fn().mockReturnValue(res); + res.json = jest.fn().mockReturnValue(res); + return res; +}; + +const mockNext = () => jest.fn() as NextFunction; + +describe("Authontication middleware", () => { + test("should throw missing authorization error", () => { + const req = mockRequest({ authorization: "" }); + const res = mockResponse(); + const next = mockNext(); + + protectRoute(req as Request, res as Response, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith({ + message: "Authorization header missing", + }); + }); + test("should return 401 if JWT_SECRET is missing", async () => { + const req = mockRequest({ + authorization: "Bearer valid.token.here", + }); + const res = mockResponse(); + const next = mockNext(); + + const originalSecret = process.env.JWT_SECRET; + delete process.env.JWT_SECRET; + await protectRoute(req as Request, res as Response, next as NextFunction); + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith({ message: "JWT_SECRET is missing" }); + + process.env.JWT_SECRET = originalSecret; + }); + test("should return 401 if token is invalid", async () => { + const req = mockRequest({ + authorization: "Bearer invalid.token.here", + }); + const res = mockResponse(); + const next = mockNext(); + + await protectRoute(req as Request, res as Response, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith({ + message: "Unauthorized request, Try again", + }); + }); +}); +describe("restrictTo middleware", () => { + let req: Partial; + let res: Partial; + let next: jest.Mock; + + beforeEach(() => { + req = { + user: { + role: "admin", // Simulate an admin user + }, + }; + res = { + status: jest.fn(), + json: jest.fn(), + }; + next = jest.fn(); + }); + + test("should allow access for permitted roles", () => { + const middleware = restrictTo("admin"); + + middleware(req as Request, res as Response, next); + + expect(next).toHaveBeenCalled(); + }); +}); diff --git a/src/__test__/user.test.ts b/src/__test__/user.test.ts index 945c7e6..3f2b960 100644 --- a/src/__test__/user.test.ts +++ b/src/__test__/user.test.ts @@ -2,13 +2,28 @@ import request from "supertest"; import app from "../../app"; import db from "../database/config/database.config"; +let userId: any; +let token: any; + describe("User", () => { let sequelizeInstance: any; beforeAll(async () => { jest.setTimeout(60000); sequelizeInstance = await db(); - await sequelizeInstance.query('TRUNCATE TABLE users RESTART IDENTITY CASCADE;'); + await sequelizeInstance.query( + "TRUNCATE TABLE users RESTART IDENTITY CASCADE;" + ); + const newUser = { + firstName: "Mugisha", + lastName: "Walmond", + email: "mugishaadminn@gmail.com", + password: process.env.TEST_USER_PASS, + role: "admin", + phone: "+13362851038", + }; + const adminRes = await request(app).post("/api/users/signup").send(newUser); + token = adminRes.body.token; }); afterAll(async () => { @@ -23,13 +38,16 @@ describe("User", () => { firstName: "Mugisha", lastName: "Walmond", email: "mugisha@gmail.com", - password: "Walmond@123", + password: process.env.TEST_USER_PASS, role: "seller", - phone: "+13362851038" + phone: "+13362851038", }; - const res = await request(app).post('/api/users/signup').send(newUser); + const res = await request(app).post("/api/users/signup").send(newUser); expect(res.statusCode).toBe(200); - expect(res.body.message).toBe('User created successfully'); + userId = res.body.data.user.id; + console.log(userId); + + expect(res.body.message).toBe("User created successfully"); }); test("user makes registration using existing email", async () => { @@ -37,13 +55,13 @@ describe("User", () => { firstName: "Mugisha", lastName: "Walmond", email: "mugisha@gmail.com", - password: "Walmond@123", + password: process.env.TEST_USER_PASS, role: "seller", - phone: "+13362851038" + phone: "+13362851038", }; - const res = await request(app).post('/api/users/signup').send(newUser); + const res = await request(app).post("/api/users/signup").send(newUser); expect(res.statusCode).toBe(409); - expect(res.body.message).toBe('User already exists. Please login again'); + expect(res.body.message).toBe("User already exists. Please login again"); }); test("user makes registration without using phone number", async () => { @@ -51,12 +69,12 @@ describe("User", () => { firstName: "Mugisha", lastName: "Walmond", email: "mugishajosep9@gmail.com", - password: "Walmond@123", + password: process.env.TEST_USER_PASS, role: "seller", }; - const res = await request(app).post('/api/users/signup').send(newUser); + const res = await request(app).post("/api/users/signup").send(newUser); expect(res.statusCode).toBe(400); - expect(res.body.data.message).toBe('Phone number is required'); + expect(res.body.data.message).toBe("Phone number is required"); }); test("user registration without role", async () => { @@ -64,51 +82,87 @@ describe("User", () => { firstName: "Mugisha", lastName: "Walmond", email: "mugishajosep9@gmail.com", - password: "Walmond@123", - phone: "+13362851038" + password: process.env.TEST_USER_PASS, + phone: "+13362851038", }; - const res = await request(app).post('/api/users/signup').send(newUser); + const res = await request(app).post("/api/users/signup").send(newUser); expect(res.statusCode).toBe(400); - expect(res.body.data.message).toBe('Role is required'); + expect(res.body.data.message).toBe("Role is required"); }); test("user registration without last name", async () => { const newUser = { firstName: "Mugisha", email: "mugishajosep9@gmail.com", - password: "Walmond@123", + password: process.env.TEST_USER_PASS, role: "buyer", - phone: "+13362851038" + phone: "+13362851038", }; - const res = await request(app).post('/api/users/signup').send(newUser); + const res = await request(app).post("/api/users/signup").send(newUser); expect(res.statusCode).toBe(400); - expect(res.body.data.message).toBe('Last name is required'); + expect(res.body.data.message).toBe("Last name is required"); }); test("user registration without first name", async () => { const newUser = { lastName: "Walmond", email: "mugishajosep9@gmail.com", - password: "Walmond@123", + password: process.env.TEST_USER_PASS, role: "buyer", - phone: "+13362851038" + phone: "+13362851038", }; - const res = await request(app).post('/api/users/signup').send(newUser); + const res = await request(app).post("/api/users/signup").send(newUser); expect(res.statusCode).toBe(400); - expect(res.body.data.message).toBe('First name is required'); + expect(res.body.data.message).toBe("First name is required"); }); test("user registration without email", async () => { const newUser = { firstName: "Mugisha", lastName: "Walmond", - password: "Walmond@123", + password: process.env.TEST_USER_PASS, role: "seller", - phone: "+13362851038" + phone: "+13362851038", }; - const res = await request(app).post('/api/users/signup').send(newUser); + const res = await request(app).post("/api/users/signup").send(newUser); expect(res.statusCode).toBe(400); - expect(res.body.data.message).toBe('Email address is required'); + expect(res.body.data.message).toBe("Email address is required"); + }); + }); + + describe("Update user role", () => { + test("update user role without login", async () => { + const res = await request(app) + .patch(`/api/users/${userId}`) + .send({ role: "seller" }); + expect(res.statusCode).toBe(401); + expect(res.body.message).toBe("Authorization header missing"); + }); + + test("update user role with invalid token", async () => { + const res = await request(app) + .patch(`/api/users/${userId}`) + .set("Authorization", `Bearer kdekefiwfgj`) + .send({ role: "seller" }); + expect(res.statusCode).toBe(401); + expect(res.body.message).toBe("Unauthorized request, Try again"); + }); + + test("update user role successful", async () => { + const res = await request(app) + .patch(`/api/users/${userId}`) + .set("Authorization", `Bearer ${token}`) + .send({ role: "seller" }); + expect(res.statusCode).toBe(201); + expect(res.body.message).toBe("User role updated successfully"); + }); + test("update user role and user not found", async () => { + const res = await request(app) + .patch(`/api/users/${"21c2e6b1-eb05-4dde-b7bb-25bad784c296"}`) + .set("Authorization", `Bearer ${token}`) + .send({ role: "seller" }); + expect(res.statusCode).toBe(404); + expect(res.body.message).toBe("User not found"); }); }); @@ -116,7 +170,7 @@ describe("User", () => { test("user logs in with correct credentials", async () => { const loginUser = { email: "mugisha@gmail.com", - password: "Walmond@123" + password: process.env.TEST_USER_PASS, }; const res = await request(app).post('/api/users/login').send(loginUser); expect(res.statusCode).toBe(200); @@ -127,7 +181,7 @@ describe("User", () => { test("user logs in with incorrect password", async () => { const loginUser = { email: "mugisha@gmail.com", - password: "wrongpassword" + password:process.env.TEST_USER_WRONG_PASS, }; const res = await request(app).post('/api/users/login').send(loginUser); expect(res.statusCode).toBe(401); @@ -137,7 +191,7 @@ describe("User", () => { test("user logs in with non-existing email", async () => { const loginUser = { email: "nonexistentemail@gmail.com", - password: "Password@123" + password: process.env.TEST_USER_PASS, }; const res = await request(app).post('/api/users/login').send(loginUser); expect(res.statusCode).toBe(401); @@ -146,7 +200,7 @@ describe("User", () => { test("user logs in without email", async () => { const loginUser = { - password: "Password@123" + password: process.env.TEST_USER_PASS }; const res = await request(app).post('/api/users/login').send(loginUser); expect(res.statusCode).toBe(400); @@ -165,13 +219,13 @@ describe("User", () => { }); describe("Testing endpoint", () => { - test('Not found for site 404', async () => { - const res = await request(app).get('/wrong-endpoint'); + test("Not found for site 404", async () => { + const res = await request(app).get("/wrong-endpoint"); expect(res.statusCode).toBe(404); }); - test('Check root route', async () => { - const res = await request(app).get('/'); + test("Check root route", async () => { + const res = await request(app).get("/"); expect(res.statusCode).toBe(200); }); }); diff --git a/src/controllers/user.controller.ts b/src/controllers/user.controller.ts index f409d61..4ebf3da 100644 --- a/src/controllers/user.controller.ts +++ b/src/controllers/user.controller.ts @@ -45,6 +45,26 @@ export const userSignup = async (req: Request, res: Response) => { } }; +export const updateRole=async (req: Request, res: Response)=>{ + const id=req.params.id; + const role=req.body.role; + const user=await UserService.getUserByid(id); + if(!user){ + return res.status(404).json({ + status: "fail", + message: "User not found", + }); + } + user.role=role; + user?.save(); + res.status(201).json({ + status: "success", + message: "User role updated successfully", + data: { + user: user, + }, + }) +} //User Login Controller const userLogin = async (req: Request, res: Response) => { diff --git a/src/database/config/sequelize.config.ts b/src/database/config/sequelize.config.ts index 0a05911..a026bde 100644 --- a/src/database/config/sequelize.config.ts +++ b/src/database/config/sequelize.config.ts @@ -13,10 +13,10 @@ if (!databaseUrl) { export const sequelize = new Sequelize(databaseUrl, { dialect: "postgres", dialectModule: pg, - dialectOptions: { - ssl: { - require: true, - rejectUnauthorized: false, - }, - }, -}); + // dialectOptions: { + // ssl: { + // require: true, + // rejectUnauthorized: false, + // }, + // }, +}); \ No newline at end of file diff --git a/src/middlewares/auth.middleware.ts b/src/middlewares/auth.middleware.ts new file mode 100644 index 0000000..dd9270a --- /dev/null +++ b/src/middlewares/auth.middleware.ts @@ -0,0 +1,60 @@ +import { NextFunction, Request, Response } from "express"; +import jwt from "jsonwebtoken"; + +declare global { + namespace Express { + interface Request { + user?: any; + } + } +} + +export const protectRoute = async ( + req: Request, + res: Response, + next: NextFunction +) => { + try { + let token; + if (!req.headers.authorization) { + return res.status(401).json({ message: "Authorization header missing" }); + } + token = req.headers.authorization.split(" ")[1]; + const jwt_secret: string | undefined = process.env.JWT_SECRET; + if (!jwt_secret) { + return res.status(401).json({ message: "JWT_SECRET is missing" }); + } + jwt.verify(token, jwt_secret, async (err, user) => { + if (err) { + return res + .status(401) + .json({ message: "Unauthorized request, Try again" }); + } else { + req.user = user; + next(); + } + }); + } catch (err) { + console.log(err, "Error occurred"); + } +}; + +export const restrictTo = (...roles: any) => { + return (req: Request, res: Response, next: NextFunction) => { + if (!roles.includes(req.user.role)) { + return res.status(403).json({ + message: "You are not authorized to perform this action", + }); + } + next(); + }; +}; + +// export const isVerfied = (req: Request, res: Response, next: NextFunction) => { +// if (!req.user.verfied) { +// return res.status(403).json({ +// message: "You are not authorized to perform this action", +// }); +// } +// next(); +// }; \ No newline at end of file diff --git a/src/routes/user.route.ts b/src/routes/user.route.ts index 48ef7a6..f7a0d90 100644 --- a/src/routes/user.route.ts +++ b/src/routes/user.route.ts @@ -1,10 +1,13 @@ import express from 'express'; -import { userSignup } from '../controllers/user.controller'; +import { updateRole, userSignup } from '../controllers/user.controller'; +import { protectRoute, restrictTo } from '../middlewares/auth.middleware'; import userLogin from '../controllers/user.controller'; import { validateUser,validateUserLogin } from '../validations/user.validate'; const userRoutes = express.Router(); userRoutes.post('/signup',validateUser,userSignup); +userRoutes.patch('/:id',protectRoute,restrictTo('admin'), updateRole); + userRoutes.post('/login', validateUserLogin, userLogin); export default userRoutes; \ No newline at end of file diff --git a/src/services/user.services.ts b/src/services/user.services.ts index fdda68e..eb9a1ba 100644 --- a/src/services/user.services.ts +++ b/src/services/user.services.ts @@ -6,6 +6,10 @@ export class UserService { return await User.create(user); } static async getUserByEmail(email:string) { - return await User.findOne({ where: { email: email } }); + return await User.findOne({ where: { email: email } }); } -} + + static async getUserByid(id:string) { + return await User.findOne({ where: { id: id } }); + } +} \ No newline at end of file diff --git a/swagger.config.ts b/swagger.config.ts index 1708ba6..7433e60 100644 --- a/swagger.config.ts +++ b/swagger.config.ts @@ -119,6 +119,22 @@ const options = { } } }, + '/api/users/{id}': { + patch: { + summary: 'Change user role', + tags: ['Authentication'], + security: [{ bearerAuth: [] }], + parameters: [ + { + name: 'id', + in: 'path', + required: true, + schema: { + type: 'string' + }, + description: 'User ID' + } + ], //User Login Route Documentation '/api/users/login': { @@ -132,6 +148,12 @@ const options = { schema: { type: 'object', properties: { + role: { + type: 'string', + example: 'admin' + } + }, + required: ['role'], email: { type: 'string', @@ -149,12 +171,15 @@ const options = { }, responses: { 201: { - description: 'OK', + description: 'Role updated successfully', content: { 'application/json': { schema: { type: 'object', properties: { + id: { type: 'string' }, + role: { type: 'string' } + }, email: { type: 'string' }, password: { type: 'string' }, @@ -172,6 +197,12 @@ const options = { }, 400: { description: 'Bad Request' + }, + 403: { + description: 'Forbidden' + }, + 404: { + description: 'User not found' } } }