diff --git a/eslint.config.mjs b/eslint.config.mjs index 31953fd..33ac94a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -6,4 +6,9 @@ import tseslint from 'typescript-eslint'; export default tseslint.config( eslint.configs.recommended, tseslint.configs.recommended, + { + rules: { + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + } + }, ); \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index e2e4a8f..786e496 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,9 +4,9 @@ import config from "./config/config"; import morgan from "./config/morgan"; import path from 'path'; import logger from './config/logger'; -import ApiError from './utils/helper/ApiError'; import error from './middleware/error'; import routes from './routes'; +import ApiError from './utils/helper/ApiError'; class App { private app: Application; @@ -38,8 +38,12 @@ class App { } private initializeErrorHandling(): void { - this.app.use(error.errorConverter); this.app.use(error.errorHandler); + this.app.use(error.errorConverter); + this.app.use((req, res, next) => { + console.log('Request passed through middleware.'); + next(); + }); } public listen(port: number): ReturnType { diff --git a/src/controllers/productController.ts b/src/controllers/productController.ts index 2d41769..bcaed29 100644 --- a/src/controllers/productController.ts +++ b/src/controllers/productController.ts @@ -35,6 +35,16 @@ export class ProductController { res.status(200).json(new ApiResponse(200, data, 'Products retrieved successfully')); } + @AsyncHandler() + async onGetByIDProducts(req: Request, res: Response) { + const data = await this.interactor.getByIdProduct(Number(req.params.id)); + if (!data) { + res.status(200).json(new ApiResponse(200, null, 'No products found')); + return; + } + res.status(200).json(new ApiResponse(200, data, 'Products retrieved successfully')); + } + @AsyncHandler() async onUpdateStock(req: Request, res: Response) { const data = await this.interactor.updateStock(parseInt(`${req.params.id}`, 10), parseInt(`${req.body.stock}`, 10)); diff --git a/src/interactors/productInteractor.ts b/src/interactors/productInteractor.ts index 77ece0e..2439dd8 100644 --- a/src/interactors/productInteractor.ts +++ b/src/interactors/productInteractor.ts @@ -38,5 +38,8 @@ export class ProductInteractor implements IProductInteractor { async getProducts(limit: number, offset: number): Promise { return await this.repository.find(limit, offset) } + async getByIdProduct(id: number): Promise { + return await this.repository.findById(id) + } } \ No newline at end of file diff --git a/src/interfaces/IProductInteractor.ts b/src/interfaces/IProductInteractor.ts index d373af6..b24f490 100644 --- a/src/interfaces/IProductInteractor.ts +++ b/src/interfaces/IProductInteractor.ts @@ -1,7 +1,8 @@ import { Product } from "../entities/Product"; export interface IProductInteractor { - createProduct(input: Partial): Promise; - updateStock(id: number, stock: number): Promise; - getProducts(limit: number, offset: number): Promise; - } \ No newline at end of file + createProduct(input: Partial): Promise; + updateStock(id: number, stock: number): Promise; + getProducts(limit: number, offset: number): Promise; + getByIdProduct(id: number): Promise; +} \ No newline at end of file diff --git a/src/interfaces/IProductRepository.ts b/src/interfaces/IProductRepository.ts index 14f5278..5d0d877 100644 --- a/src/interfaces/IProductRepository.ts +++ b/src/interfaces/IProductRepository.ts @@ -4,4 +4,5 @@ export interface IProductRepository { create(data: Product): Promise; update(id: number, data: Partial): Promise; find(limit: number, offset: number): Promise; + findById(id: number): Promise; } \ No newline at end of file diff --git a/src/middleware/error.ts b/src/middleware/error.ts index 5ab4a8d..b13d2ac 100644 --- a/src/middleware/error.ts +++ b/src/middleware/error.ts @@ -40,36 +40,44 @@ class error { static errorHandler( err: ApiError, req: Request, - res: Response + res: Response, + _next: NextFunction // Retain this even if unused ): void { - let { statusCode, message } = err; - - // Production environment: Ensure only operational errors are shown - if (config.env === 'production' && !err.isOperational) { + // Extract error details with fallback defaults + let { statusCode = 500, message = "Internal server error" } = err; + + // Production: Ensure only operational errors reveal their messages + if (config.env === "production" && !err.isOperational) { statusCode = 500; - message = "Internal server error"; + message = "An unexpected error occurred"; } - + + // Attach error message to response locals for potential templating res.locals.errorMessage = err.message; - - // Response structure + + // Construct the response payload const response = { code: statusCode, message, - ...(config.env === 'development' && { stack: err.stack }) + ...(config.env === "development" && err.stack && { stack: err.stack }), // Include stack trace only in development + ...(err.errors && { errors: err.errors.map((e: Error) => e.message) }), // Include nested validation errors if any }; - - // Log error in development - if (config.env === 'development') { - logger.error(err); - } - - // Ensure the response is sent - res.status(statusCode).send(response); - - // Don't call next() unless you want to propagate the error further - // If this is the final middleware, remove next() + + // Log the error in all environments + logger.error({ + message: err.message, + stack: err.stack, + statusCode, + errors: err.errors || [], + }); + + // Send the response + res.status(statusCode).json(response); + + // Do not call `next()` here, as this is the final error handler. } + + } export default error; diff --git a/src/repositories/productRepository.ts b/src/repositories/productRepository.ts index 25b1482..3db00ec 100644 --- a/src/repositories/productRepository.ts +++ b/src/repositories/productRepository.ts @@ -40,4 +40,8 @@ export class ProductRepository implements IProductRepository { order: { id: "ASC" }, // Optional: Add sorting for consistent results }); } + + async findById(id: number): Promise { + return await this.productRepository.findOneOrFail({ where: { id } }); + } } diff --git a/src/routes/productRoutes.ts b/src/routes/productRoutes.ts index 4687845..a8faac8 100644 --- a/src/routes/productRoutes.ts +++ b/src/routes/productRoutes.ts @@ -36,5 +36,6 @@ const productController = container.get(INTERFACE_TYPE.Produc router.post('/', productController.onCreateProduct.bind(productController)); router.get('/', productController.onGetProducts.bind(productController)); router.patch('/:id', productController.onUpdateStock.bind(productController)); +router.get('/:id', productController.onGetByIDProducts.bind(productController)); export default router; \ No newline at end of file