From 4d99acdd071c024154e24f94f6582a7ba50d2a10 Mon Sep 17 00:00:00 2001 From: Musa Khalid <112591148+Mkalbani@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:34:49 +0000 Subject: [PATCH 1/3] added global validation pipe and exception filter, and implement test validation controller --- package-lock.json | 4 +-- src/app.module.ts | 3 +- src/main.ts | 14 ++++++++- src/test-validation.controller.ts | 18 ++++++++++++ src/utils/all-exceptions.filter.ts | 47 ++++++++++++++++++++++++++++++ 5 files changed, 81 insertions(+), 5 deletions(-) create mode 100644 src/test-validation.controller.ts create mode 100644 src/utils/all-exceptions.filter.ts diff --git a/package-lock.json b/package-lock.json index 6345d75..e654f94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5752,14 +5752,12 @@ "node_modules/class-transformer": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", - "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT" + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" }, "node_modules/class-validator": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.2.tgz", "integrity": "sha512-3kMVRF2io8N8pY1IFIXlho9r8IPUUIfHe2hYVtiebvAzU2XeQFXTv+XI4WX+TnXmtwXMDcjngcpkiPM0O9PvLw==", - "license": "MIT", "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.11.1", diff --git a/src/app.module.ts b/src/app.module.ts index 9ad4b23..4ddb37a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -14,6 +14,7 @@ import { AuthModule } from './auth/auth.module'; import { UserModule } from './user/user.module'; import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { TestValidationController } from './test-validation.controller'; import { NotificationsModule } from './notifications/notifications.module'; import { OffersModule } from './offers/offers.module'; @@ -35,7 +36,7 @@ import { OffersModule } from './offers/offers.module'; NotificationsModule, OffersModule, ], - controllers: [AppController], + controllers: [AppController, TestValidationController], providers: [AppService], }) export class AppModule {} diff --git a/src/main.ts b/src/main.ts index f27c766..62300a9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -16,6 +16,18 @@ async function bootstrap() { credentials: true, }); - await app.listen(process.env.PORT ?? 3000); + // Global Validation Pipe + const { ValidationPipe } = await import('@nestjs/common'); + app.useGlobalPipes(new ValidationPipe({ + whitelist: true, + forbidNonWhitelisted: true, + transform: true, + })); + + // Global Exception Filter + const { AllExceptionsFilter } = await import('./utils/all-exceptions.filter'); + app.useGlobalFilters(new AllExceptionsFilter()); + + await app.listen(process.env.PORT ?? 3000); } bootstrap(); diff --git a/src/test-validation.controller.ts b/src/test-validation.controller.ts new file mode 100644 index 0000000..2fe60e8 --- /dev/null +++ b/src/test-validation.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Post, Body } from '@nestjs/common'; +import { IsEmail, IsNotEmpty } from 'class-validator'; + +class TestDto { + @IsEmail() + email: string; + + @IsNotEmpty() + name: string; +} + +@Controller('test-validation') +export class TestValidationController { + @Post() + test(@Body() dto: TestDto) { + return { message: 'Validation passed', data: dto }; + } +} diff --git a/src/utils/all-exceptions.filter.ts b/src/utils/all-exceptions.filter.ts new file mode 100644 index 0000000..34f9b98 --- /dev/null +++ b/src/utils/all-exceptions.filter.ts @@ -0,0 +1,47 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { Request, Response } from 'express'; +import { Logger } from '@nestjs/common'; + +@Catch() +export class AllExceptionsFilter implements ExceptionFilter { + private readonly logger = new Logger(AllExceptionsFilter.name); + + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + + let status = HttpStatus.INTERNAL_SERVER_ERROR; + let message = 'Internal server error'; + let errorResponse: any = {}; + + if (exception instanceof HttpException) { + status = exception.getStatus(); + const exceptionResponse = exception.getResponse(); + if (typeof exceptionResponse === 'string') { + message = exceptionResponse; + } else if (typeof exceptionResponse === 'object') { + errorResponse = exceptionResponse; + message = (exceptionResponse as any).message || message; + } + } else if (exception instanceof Error) { + message = exception.message; + } + + this.logger.error(`Status: ${status} Error: ${message}`, exception instanceof Error ? exception.stack : ''); + + response.status(status).json({ + statusCode: status, + timestamp: new Date().toISOString(), + path: request.url, + message, + ...errorResponse, + }); + } +} From 9c53dd4767055f1acff7da52f9eafa9be0d3be31 Mon Sep 17 00:00:00 2001 From: Musa Khalid <112591148+Mkalbani@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:52:53 +0000 Subject: [PATCH 2/3] test: add unit tests for NotificationsService --- .../notifications.service.spec.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 src/notifications/notifications.service.spec.ts diff --git a/src/notifications/notifications.service.spec.ts b/src/notifications/notifications.service.spec.ts new file mode 100644 index 0000000..74fb598 --- /dev/null +++ b/src/notifications/notifications.service.spec.ts @@ -0,0 +1,25 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { NotificationsService } from './notifications.service'; +import { NotificationsGateway } from './notifications.gateway'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { Notification } from './entities/notification.entity'; +import { Repository } from 'typeorm'; + +describe('NotificationsService', () => { + let service: NotificationsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + NotificationsService, + NotificationsGateway, + { provide: getRepositoryToken(Notification), useClass: Repository }, + ], + }).compile(); + service = module.get(NotificationsService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); From 5dd4372d55fd3a4d167683ec38fe9cda72aeb4c7 Mon Sep 17 00:00:00 2001 From: Musa Khalid <112591148+Mkalbani@users.noreply.github.com> Date: Tue, 12 Aug 2025 12:00:45 +0000 Subject: [PATCH 3/3] update import paths for User and throttlerConfig in auth.service.ts and password-reset.module.ts --- src/auth/auth.service.ts | 2 +- src/password-reset/password-reset.module.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 60b194b..c6b4568 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -2,7 +2,7 @@ import { Injectable, BadRequestException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { RegisterDto } from './dtos/register.dto'; -import { User } from 'src/user/user.entity'; +import { User } from '../user/user.entity'; import * as bcrypt from 'bcryptjs'; import { generateVerificationToken, verifyJwtToken } from '../utils/jwt.util'; import { MailService } from '../mail/mail.service'; diff --git a/src/password-reset/password-reset.module.ts b/src/password-reset/password-reset.module.ts index 8c718c4..c36fd92 100644 --- a/src/password-reset/password-reset.module.ts +++ b/src/password-reset/password-reset.module.ts @@ -5,8 +5,8 @@ import { PasswordResetController } from './password-reset.controller'; import { PasswordResetService } from './password-reset.service'; import { PasswordResetToken } from './entities/password-reset-token.entity'; import { EmailModule } from '../email/email.module'; -import { throttlerConfig } from 'src/config/throttler.config'; -import { UserModule } from 'src/user/user.module'; +import { throttlerConfig } from '../config/throttler.config'; +import { UserModule } from '../user/user.module'; @Module({ imports: [