From 82d5d6e507a61681291e10f1622fd24156309fe0 Mon Sep 17 00:00:00 2001 From: Roshan Paudel Date: Sat, 12 Oct 2024 19:31:52 +0545 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9E=20FIX:=20refresh=20token=20and=20m?= =?UTF-8?q?iddleware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/auth/auth.controller.ts | 1 + src/app/auth/auth.service.ts | 13 +++--- src/app/index.ts | 15 ++++--- src/app/sales/product/product.controller.ts | 15 +++++++ src/app/sales/product/product.docs.ts | 0 src/app/sales/product/product.model.ts | 44 +++++++++++++++++++ src/app/sales/product/product.routes.ts | 21 +++++++++ src/app/sales/product/product.service.ts | 11 +++++ src/middleware.ts | 47 +++++++++++++++++++++ src/schema/product.schema.ts | 11 +++++ src/utils/errors.ts | 1 - 11 files changed, 166 insertions(+), 13 deletions(-) create mode 100644 src/app/sales/product/product.controller.ts create mode 100644 src/app/sales/product/product.docs.ts create mode 100644 src/app/sales/product/product.model.ts create mode 100644 src/app/sales/product/product.routes.ts create mode 100644 src/app/sales/product/product.service.ts create mode 100644 src/middleware.ts create mode 100644 src/schema/product.schema.ts diff --git a/src/app/auth/auth.controller.ts b/src/app/auth/auth.controller.ts index e4caeef..f9cbb27 100644 --- a/src/app/auth/auth.controller.ts +++ b/src/app/auth/auth.controller.ts @@ -15,6 +15,7 @@ export class AuthController { refresh = asyncWrapper(async (req, res) => { const { refreshToken } = req.body; const accessToken = this.service.refreshAccessToken(refreshToken); + return res.json({ message: 'Token refreshed successfully', data: { accessToken }, diff --git a/src/app/auth/auth.service.ts b/src/app/auth/auth.service.ts index 4fc9ef0..1fcb157 100644 --- a/src/app/auth/auth.service.ts +++ b/src/app/auth/auth.service.ts @@ -1,5 +1,5 @@ import jwt from 'jsonwebtoken'; -import bcrypt from 'bcryptjs'; // For password hashing +import bcrypt from 'bcryptjs'; import { BadRequestError, ForbiddenError, @@ -36,7 +36,7 @@ export class AuthService { generateRefreshToken(user: User) { return jwt.sign( - { id: user.id, email: user.email }, + { id: user.id, email: user.email, role: user.role }, this.refreshTokenSecret, { expiresIn: this.refreshTokenExpiry }, ); @@ -60,13 +60,14 @@ export class AuthService { refreshAccessToken(refreshToken: string) { try { - const decoded: any = this.verifyRefreshToken(refreshToken); + const user: any = this.verifyRefreshToken(refreshToken); return this.generateAccessToken({ - id: decoded.id, - email: decoded.email, - role: decoded.role || 'user', + id: user.id, + email: user.email, + role: user.role || 'user', }); } catch (err) { + console.log(err); throw new BadRequestError('Could not refresh access token'); } } diff --git a/src/app/index.ts b/src/app/index.ts index 066ae1a..6b05f6a 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -13,6 +13,8 @@ import swaggerUi from 'swagger-ui-express'; import { swaggerConfig } from '../configs/swagger'; import { otpRoutes } from './otp/opt.route'; import { authRoutes } from './auth/auth.routes'; +import productRoutes from './sales/product/product.routes'; +import { isAuthencticated } from '../middleware'; export class App { public app: Application; @@ -35,22 +37,23 @@ export class App { } private setRoutes() { - this.app.use('/user', userRoutes); - this.app.use('/auth', authRoutes); - this.app.use('/otp', otpRoutes); - if (process.env.ENV === 'development') { - this.app.use('*', (req, res, next) => { + this.app.use('*', (req, _, next) => { console.log('BODY', req.body); console.log('QUERY', req.query); next(); }); + this.app.use('/user', userRoutes); + this.app.use('/auth', authRoutes); + this.app.use('/otp', otpRoutes); + this.app.use('/product', isAuthencticated, productRoutes); + this.app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerConfig)); } } private handleErrors() { - this.app.get('*', () => { + this.app.use('*', () => { throw new NotFoundError(); }); diff --git a/src/app/sales/product/product.controller.ts b/src/app/sales/product/product.controller.ts new file mode 100644 index 0000000..eaec857 --- /dev/null +++ b/src/app/sales/product/product.controller.ts @@ -0,0 +1,15 @@ +import { asyncWrapper } from '../../../utils/wrapper'; +import { ProductService } from './product.service'; + +export class ProductController { + private service: ProductService; + + constructor() { + this.service = new ProductService(); + } + + addnewProduct = asyncWrapper(async (req, res) => { + await this.service.addNewProduct(req.body); + return res.json({ message: 'Product created successfully' }); + }); +} diff --git a/src/app/sales/product/product.docs.ts b/src/app/sales/product/product.docs.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/app/sales/product/product.model.ts b/src/app/sales/product/product.model.ts new file mode 100644 index 0000000..656ca02 --- /dev/null +++ b/src/app/sales/product/product.model.ts @@ -0,0 +1,44 @@ +import { Schema, model } from 'mongoose'; + +// Define the schema for the Product +const productSchema = new Schema( + { + user: { + type: Schema.Types.ObjectId, + ref: 'User', // Reference to the User model + required: true, + }, + batchNo: { + type: String, + required: true, + }, + qty: { + type: Number, + min: 0, + default: 0, + }, + reorderQty: { + type: Number, + min: 0, + default: 0, + }, + salesPrice: { + type: Number, + required: true, + min: 0, + }, + costPrice: { + type: Number, + required: true, + min: 0, + }, + }, + { + timestamps: true, + }, +); + +// Create the Product model +const ProductModel = model('Product', productSchema); + +export default ProductModel; diff --git a/src/app/sales/product/product.routes.ts b/src/app/sales/product/product.routes.ts new file mode 100644 index 0000000..d24f241 --- /dev/null +++ b/src/app/sales/product/product.routes.ts @@ -0,0 +1,21 @@ +import { Router } from 'express'; +import { ProductController } from './product.controller'; + +class ProductRouter { + public router: Router; + + private controller: ProductController; + + constructor() { + this.router = Router(); + this.controller = new ProductController(); + this.mountRoutes(); + } + + mountRoutes() { + this.router.post('/', this.controller.addnewProduct); + } +} + +const productRoutes = new ProductRouter().router; +export default productRoutes; diff --git a/src/app/sales/product/product.service.ts b/src/app/sales/product/product.service.ts new file mode 100644 index 0000000..83ce942 --- /dev/null +++ b/src/app/sales/product/product.service.ts @@ -0,0 +1,11 @@ +import ProductModel from './product.model'; +import { productSchema, TProductSchema } from '../../../schema/product.schema'; +import { BadRequestError } from '../../../utils/exceptions'; +export class ProductService { + async addNewProduct(payload: TProductSchema) { + const { data, success } = productSchema.safeParse(payload); + if (!success) throw new BadRequestError('Invalid payload format'); + const newProduct = new ProductModel(data); + await newProduct.save(); + } +} diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..9f4ada6 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,47 @@ +import { Request, Response, NextFunction } from 'express'; +import jwt from 'jsonwebtoken'; +import { ForbiddenError, UnauthorizedError } from './utils/exceptions'; +import UserModel from './app/user/user.model'; + +export const isAuthencticated = ( + req: Request, + res: Response, + next: NextFunction, +) => { + let token = req.headers.authorization; + if (!token) { + throw new UnauthorizedError('Token is required'); + } + if (token.startsWith('Bearer ')) { + token = token.slice(7, token.length).trim(); + } + + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + console.log('decoded', decoded); + req.params.userId = (decoded as any).userId; + next(); + } catch (err) { + console.error(err); + res.status(401).json({ message: 'Invalid or expired token' }); + } +}; + +// export const isAdmin = async ( +// req: Request, +// res: Response, +// next: NextFunction, +// ) => { +// try { +// const userId = req.params.userId; +// const user = await UserModel.findById(userId); +// if (user?.isAdmin) { +// next(); +// } else { +// throw new ForbiddenError('Only Admins are allowed'); +// } +// } catch (err: any) { +// console.error(err); +// res.status(403).json({ message: err.message }); +// } +// }; diff --git a/src/schema/product.schema.ts b/src/schema/product.schema.ts new file mode 100644 index 0000000..2d13111 --- /dev/null +++ b/src/schema/product.schema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +export const productSchema = z.object({ + name: z.string().min(1), + stock: z.coerce.number().min(0), + reorderLevel: z.coerce.number().min(0), + salesPrice: z.coerce.number().min(0), + costPrice: z.coerce.number().min(0), +}); + +export type TProductSchema = z.infer; diff --git a/src/utils/errors.ts b/src/utils/errors.ts index e1959aa..922cc2a 100644 --- a/src/utils/errors.ts +++ b/src/utils/errors.ts @@ -7,7 +7,6 @@ export function handleError(err: Error): { if (err instanceof CustomError) { return { statusCode: err.statusCode, error: err.message }; } else { - console.log(err); return { statusCode: 500, error: 'Internal Server Error' }; } }