From f6932cf77215aac1be2279862736c534fcdf5fe8 Mon Sep 17 00:00:00 2001 From: Roshan Paudel Date: Sun, 13 Oct 2024 07:18:18 +0545 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=8C=20IMPROVE:=20added=20product?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/index.ts | 16 +++---- src/app/sales/product/product.controller.ts | 3 +- src/app/sales/product/product.docs.ts | 50 +++++++++++++++++++++ src/app/sales/product/product.model.ts | 41 +++++++++++------ src/app/sales/product/product.service.ts | 21 ++++++++- src/middleware.ts | 38 ++++------------ src/types/express.ts | 5 +++ src/utils/wrapper.ts | 16 +++++-- 8 files changed, 129 insertions(+), 61 deletions(-) create mode 100644 src/types/express.ts diff --git a/src/app/index.ts b/src/app/index.ts index 6b05f6a..691f1e4 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -37,17 +37,11 @@ export class App { } private setRoutes() { - if (process.env.ENV === 'development') { - 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('/user', userRoutes); + this.app.use('/auth', authRoutes); + this.app.use('/otp', otpRoutes); + this.app.use('/product', isAuthencticated, productRoutes); + if ((process.env.ENV = 'development')) { this.app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerConfig)); } } diff --git a/src/app/sales/product/product.controller.ts b/src/app/sales/product/product.controller.ts index eaec857..f648f4d 100644 --- a/src/app/sales/product/product.controller.ts +++ b/src/app/sales/product/product.controller.ts @@ -9,7 +9,8 @@ export class ProductController { } addnewProduct = asyncWrapper(async (req, res) => { - await this.service.addNewProduct(req.body); + const user = req.userId; + await this.service.addNewProduct(req.body, user); 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 index e69de29..3025491 100644 --- a/src/app/sales/product/product.docs.ts +++ b/src/app/sales/product/product.docs.ts @@ -0,0 +1,50 @@ +/** + * @swagger + * tags: + * name: Product + * description: API for managing products + */ + +/** + * @swagger + * /product: + * post: + * summary: Add new product + * description: Adds a new product with its details. + * tags: [Product] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * example: "Product Name" # Example product name + * stock: + * type: number + * example: 100 # Example stock value + * reorderLevel: + * type: number + * example: 10 # Example reorder level + * salesPrice: + * type: number + * example: 99.99 # Example sales price + * costPrice: + * type: number + * example: 79.99 # Example cost price + * required: + * - name + * - stock + * - reorderLevel + * - salesPrice + * - costPrice + * responses: + * 201: + * $ref: '#/components/responses/201' + * 400: + * $ref: '#/components/responses/400' + * 500: + * $ref: '#/components/responses/500' + */ diff --git a/src/app/sales/product/product.model.ts b/src/app/sales/product/product.model.ts index 656ca02..5082b4d 100644 --- a/src/app/sales/product/product.model.ts +++ b/src/app/sales/product/product.model.ts @@ -1,15 +1,9 @@ import { Schema, model } from 'mongoose'; -// Define the schema for the Product -const productSchema = new Schema( +const batchSchema = new Schema( { - user: { - type: Schema.Types.ObjectId, - ref: 'User', // Reference to the User model - required: true, - }, batchNo: { - type: String, + type: Number, // Batch number is unique for each product required: true, }, qty: { @@ -17,11 +11,6 @@ const productSchema = new Schema( min: 0, default: 0, }, - reorderQty: { - type: Number, - min: 0, - default: 0, - }, salesPrice: { type: Number, required: true, @@ -33,12 +22,36 @@ const productSchema = new Schema( min: 0, }, }, + { id: false, timestamps: true }, +); + +// Define the schema for the Product +const productSchema = new Schema( + { + user: { + type: Schema.Types.ObjectId, + ref: 'User', // Reference to the User model + required: true, + }, + reorderLevel: { + type: Number, + min: 0, + default: 0, + }, + name: { + type: String, + required: true, + unique: true, // Ensure product name is unique + }, + batches: [batchSchema], // Array of batches + }, { timestamps: true, }, ); +productSchema.index({ user: 1, name: 1 }, { unique: true }); + // Create the Product model const ProductModel = model('Product', productSchema); - export default ProductModel; diff --git a/src/app/sales/product/product.service.ts b/src/app/sales/product/product.service.ts index 83ce942..8276273 100644 --- a/src/app/sales/product/product.service.ts +++ b/src/app/sales/product/product.service.ts @@ -1,11 +1,28 @@ import ProductModel from './product.model'; import { productSchema, TProductSchema } from '../../../schema/product.schema'; import { BadRequestError } from '../../../utils/exceptions'; + export class ProductService { - async addNewProduct(payload: TProductSchema) { + async addNewProduct(payload: TProductSchema, user: string) { const { data, success } = productSchema.safeParse(payload); if (!success) throw new BadRequestError('Invalid payload format'); - const newProduct = new ProductModel(data); + + const batches = [ + { + batchNo: 1, + qty: data.stock, + salesPrice: data.salesPrice, + costPrice: data.costPrice, + }, + ]; + + const newProduct = new ProductModel({ + user, + name: data.name, + reorderLevel: data.reorderLevel, + batches, + }); + await newProduct.save(); } } diff --git a/src/middleware.ts b/src/middleware.ts index 9f4ada6..054b794 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,10 +1,10 @@ -import { Request, Response, NextFunction } from 'express'; +import { Response, NextFunction } from 'express'; import jwt from 'jsonwebtoken'; -import { ForbiddenError, UnauthorizedError } from './utils/exceptions'; -import UserModel from './app/user/user.model'; +import { UnauthorizedError } from './utils/exceptions'; +import { IRequestWithUser } from './types/express'; export const isAuthencticated = ( - req: Request, + req: IRequestWithUser, res: Response, next: NextFunction, ) => { @@ -12,36 +12,16 @@ export const isAuthencticated = ( 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(); + const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET); + req.userId = (decoded as any).id; + return next(); } catch (err) { - console.error(err); - res.status(401).json({ message: 'Invalid or expired token' }); + return res.status(401).json({ error: '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/types/express.ts b/src/types/express.ts new file mode 100644 index 0000000..0b01b9d --- /dev/null +++ b/src/types/express.ts @@ -0,0 +1,5 @@ +import { Request } from 'express'; + +export interface IRequestWithUser extends Request { + userId?: string; +} diff --git a/src/utils/wrapper.ts b/src/utils/wrapper.ts index 5f55907..5858d6c 100644 --- a/src/utils/wrapper.ts +++ b/src/utils/wrapper.ts @@ -1,11 +1,19 @@ -import { NextFunction, Request, Response, RequestHandler } from 'express'; +import { NextFunction, Response, RequestHandler } from 'express'; +import { IRequestWithUser } from '../types/express'; -export const asyncWrapper = (handler: RequestHandler) => { - return async (req: Request, res: Response, next: NextFunction) => { +export const asyncWrapper = ( + handler: ( + req: IRequestWithUser, + res: Response, + next: NextFunction, + ) => Promise, +): RequestHandler => { + return async (req: IRequestWithUser, res: Response, next: NextFunction) => { try { await handler(req, res, next); } catch (error) { - next(error); // Pass any caught errors to Express error handler + console.error(error); + next(error); } }; };