Skip to content

Commit

Permalink
👌 IMPROVE: added product
Browse files Browse the repository at this point in the history
  • Loading branch information
hyper-dot committed Oct 13, 2024
1 parent 82d5d6e commit f6932cf
Show file tree
Hide file tree
Showing 8 changed files with 129 additions and 61 deletions.
16 changes: 5 additions & 11 deletions src/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/sales/product/product.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' });
});
}
50 changes: 50 additions & 0 deletions src/app/sales/product/product.docs.ts
Original file line number Diff line number Diff line change
@@ -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'
*/
41 changes: 27 additions & 14 deletions src/app/sales/product/product.model.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
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: {
type: Number,
min: 0,
default: 0,
},
reorderQty: {
type: Number,
min: 0,
default: 0,
},
salesPrice: {
type: Number,
required: true,
Expand All @@ -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;
21 changes: 19 additions & 2 deletions src/app/sales/product/product.service.ts
Original file line number Diff line number Diff line change
@@ -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();
}
}
38 changes: 9 additions & 29 deletions src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,27 @@
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,
) => {
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();
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 });
// }
// };
5 changes: 5 additions & 0 deletions src/types/express.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Request } from 'express';

export interface IRequestWithUser extends Request {
userId?: string;
}
16 changes: 12 additions & 4 deletions src/utils/wrapper.ts
Original file line number Diff line number Diff line change
@@ -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<any>,
): 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);
}
};
};

0 comments on commit f6932cf

Please sign in to comment.