diff --git a/libs/common/src/auth/services/auth.service.ts b/libs/common/src/auth/services/auth.service.ts index af9d047..28940f9 100644 --- a/libs/common/src/auth/services/auth.service.ts +++ b/libs/common/src/auth/services/auth.service.ts @@ -6,6 +6,7 @@ import { UserRepository } from '../../../../common/src/modules/user/repositories import { UserSessionRepository } from '../../../../common/src/modules/session/repositories/session.repository'; import { UserSessionEntity } from '../../../../common/src/modules/session/entities'; import { ConfigService } from '@nestjs/config'; +import { OAuth2Client } from 'google-auth-library'; @Injectable() export class AuthService { diff --git a/libs/common/src/common.module.ts b/libs/common/src/common.module.ts index 8c3e78c..d156de3 100644 --- a/libs/common/src/common.module.ts +++ b/libs/common/src/common.module.ts @@ -6,10 +6,16 @@ import { AuthModule } from './auth/auth.module'; import { ConfigModule } from '@nestjs/config'; import config from './config'; import { EmailService } from './util/email.service'; - +import { ErrorMessageService } from 'src/error-message/error-message.service'; @Module({ - providers: [CommonService, EmailService], - exports: [CommonService, HelperModule, AuthModule, EmailService], + providers: [CommonService, EmailService, ErrorMessageService], + exports: [ + CommonService, + HelperModule, + AuthModule, + EmailService, + ErrorMessageService, + ], imports: [ ConfigModule.forRoot({ load: config, diff --git a/src/common/filters/http-exception/http-exception.filter.spec.ts b/src/common/filters/http-exception/http-exception.filter.spec.ts new file mode 100644 index 0000000..8f016dd --- /dev/null +++ b/src/common/filters/http-exception/http-exception.filter.spec.ts @@ -0,0 +1,7 @@ +import { HttpExceptionFilter } from './http-exception.filter'; + +describe('HttpExceptionFilter', () => { + it('should be defined', () => { + expect(new HttpExceptionFilter()).toBeDefined(); + }); +}); diff --git a/src/common/filters/http-exception/http-exception.filter.ts b/src/common/filters/http-exception/http-exception.filter.ts new file mode 100644 index 0000000..c587769 --- /dev/null +++ b/src/common/filters/http-exception/http-exception.filter.ts @@ -0,0 +1,38 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost, + HttpException, + HttpStatus, +} from '@nestjs/common'; +import { Response } from 'express'; + +@Catch() +export class HttpExceptionFilter implements ExceptionFilter { + catch(exception: unknown, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = + exception instanceof HttpException + ? exception.getStatus() + : HttpStatus.INTERNAL_SERVER_ERROR; + + const message = + exception instanceof HttpException + ? exception.getResponse() + : 'Internal server error'; + + const errorResponse = { + statusCode: status, + timestamp: new Date().toISOString(), + path: request.url, + error: message, + }; + + // Optionally log the exception + console.error(`Error occurred:`, exception); + + response.status(status).json(errorResponse); + } +} diff --git a/src/error-message/error-message.service.spec.ts b/src/error-message/error-message.service.spec.ts new file mode 100644 index 0000000..ae9c353 --- /dev/null +++ b/src/error-message/error-message.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ErrorMessageService } from './error-message.service'; + +describe('ErrorMessageService', () => { + let service: ErrorMessageService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ErrorMessageService], + }).compile(); + + service = module.get(ErrorMessageService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/error-message/error-message.service.ts b/src/error-message/error-message.service.ts new file mode 100644 index 0000000..83f7497 --- /dev/null +++ b/src/error-message/error-message.service.ts @@ -0,0 +1,40 @@ +// src/common/services/error-message.service.ts +import { Injectable } from '@nestjs/common'; +type ErrorMessageKey = keyof typeof ErrorMessageService.prototype.messages; + +@Injectable() +export class ErrorMessageService { + public readonly messages = { + NOT_FOUND: '{entity} not found', // Single key for "not found" errors + INVALID_MINUTES: 'Invalid minutes value', + INVALID_TIME_FORMAT: 'Invalid time format', + USER_NOT_FOUND: '{entity} not found', // Can reuse NOT_FOUND if desired + INTERNAL_SERVER_ERROR: 'Internal server error', + ERROR_ADDING_TEMP_PASSWORD: + 'Error adding {type} temporary password from Tuya', + INVALID_UUID: 'Invalid {entity} UUID', + USER_ALREADY_BELONGS: 'This user already belongs to this {entity}', + USER_HAS_NO_ENTITIES: 'This user has no {entity}', + DEVICE_OPERATION_FAILED: 'All device operations failed', + REQUEST_FAILED: 'Error processing {operation} request', + COOLDOWN_ERROR: + 'Please wait {time} more seconds before requesting a new OTP.', + }; + + getMessage( + key: ErrorMessageKey, + params?: Record, + ): string { + let message = this.messages[key] || 'Unknown error'; + + // Replace placeholders with provided params + if (params) { + Object.keys(params).forEach((param) => { + const regex = new RegExp(`{${param}}`, 'g'); + message = message.replace(regex, params[param].toString()); + }); + } + + return message; + } +} diff --git a/src/main.ts b/src/main.ts index 894ecd1..6dbfc45 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,6 +6,7 @@ import { setupSwaggerAuthentication } from '../libs/common/src/util/user-auth.sw import { ValidationPipe } from '@nestjs/common'; import { json, urlencoded } from 'body-parser'; import { SeederService } from '@app/common/seed/services/seeder.service'; +import { HttpExceptionFilter } from './common/filters/http-exception/http-exception.filter'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -15,6 +16,7 @@ async function bootstrap() { // Set the body parser limit to 1 MB app.use(json({ limit: '1mb' })); app.use(urlencoded({ limit: '1mb', extended: true })); + app.useGlobalFilters(new HttpExceptionFilter()); app.use( rateLimit({ diff --git a/src/region/region.module.ts b/src/region/region.module.ts index c0da5ab..22eaa2d 100644 --- a/src/region/region.module.ts +++ b/src/region/region.module.ts @@ -3,9 +3,10 @@ import { RegionService } from './services/region.service'; import { RegionController } from './controllers/region.controller'; import { ConfigModule } from '@nestjs/config'; import { RegionRepository } from '@app/common/modules/region/repositories'; +import { CommonModule } from '@app/common'; @Module({ - imports: [ConfigModule], + imports: [ConfigModule, CommonModule], controllers: [RegionController], providers: [RegionService, RegionRepository], exports: [RegionService], diff --git a/src/region/services/region.service.ts b/src/region/services/region.service.ts index 580f285..9061e0a 100644 --- a/src/region/services/region.service.ts +++ b/src/region/services/region.service.ts @@ -2,23 +2,33 @@ import { BadRequestException, HttpException, HttpStatus, + Inject, Injectable, } from '@nestjs/common'; import { RegionRepository } from '@app/common/modules/region/repositories'; +import { ErrorMessageService } from 'src/error-message/error-message.service'; @Injectable() export class RegionService { - constructor(private readonly regionRepository: RegionRepository) {} + constructor( + private readonly regionRepository: RegionRepository, + @Inject(ErrorMessageService) + private readonly errorMessageService: ErrorMessageService, + ) {} async getAllRegions() { try { const regions = await this.regionRepository.find(); - return regions; } catch (err) { if (err instanceof BadRequestException) { throw err; // Re-throw BadRequestException } else { - throw new HttpException('Regions found', HttpStatus.NOT_FOUND); + throw new HttpException( + this.errorMessageService.getMessage('NOT_FOUND', { + entity: 'Regions', + }), + HttpStatus.NOT_FOUND, + ); } } }