From b6f4107c8a82876f43530e0775ee44e49904580a Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Tue, 10 Jun 2025 06:00:56 +0300 Subject: [PATCH] add decorators, filters, interceptors, guards, pipes --- src/app.controller.ts | 26 ++++++++++++++ src/app.module.ts | 3 +- src/common/decorators/user-agent.decorator.ts | 9 +++++ src/common/filters/all-exceptions.filter.ts | 34 +++++++++++++++++++ src/common/guards/auth.guard.ts | 24 +++++++++++++ .../interceptors/response.interceptor.ts | 22 ++++++++++++ src/common/pipes/string-to-lowercase.pipe.ts | 12 +++++++ src/main.ts | 4 +++ 8 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 src/app.controller.ts create mode 100644 src/common/decorators/user-agent.decorator.ts create mode 100644 src/common/filters/all-exceptions.filter.ts create mode 100644 src/common/guards/auth.guard.ts create mode 100644 src/common/interceptors/response.interceptor.ts create mode 100644 src/common/pipes/string-to-lowercase.pipe.ts diff --git a/src/app.controller.ts b/src/app.controller.ts new file mode 100644 index 0000000..010034b --- /dev/null +++ b/src/app.controller.ts @@ -0,0 +1,26 @@ +import { + Body, + Controller, + Get, + Post, + UseGuards, + UsePipes, +} from '@nestjs/common'; +import { UserAgentDecorator } from './common/decorators/user-agent.decorator'; +import { AuthGuard } from './common/guards/auth.guard'; +import { StringToLowercasePipe } from './common/pipes/string-to-lowercase.pipe'; + +@Controller() +export class AppController { + @UsePipes(StringToLowercasePipe) + @Post() + create(@Body('title') title: string) { + return `Movie ${title}`; + } + + @UseGuards(AuthGuard) + @Get() + getProfile(@UserAgentDecorator() userAgent: string) { + return { name: 'FirstName', age: 22, userAgent }; + } +} diff --git a/src/app.module.ts b/src/app.module.ts index c47db84..3752d2d 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { AppController } from './app.controller'; import { TaskModule } from './task/task.module'; import { MovieModule } from './movie/movie.module'; import { ReviewModule } from './review/review.module'; @@ -17,7 +18,7 @@ import { PrismaModule } from './prisma/prisma.module'; ReviewModule, ActorModule, ], - controllers: [], + controllers: [AppController], providers: [], }) export class AppModule {} diff --git a/src/common/decorators/user-agent.decorator.ts b/src/common/decorators/user-agent.decorator.ts new file mode 100644 index 0000000..dcfea24 --- /dev/null +++ b/src/common/decorators/user-agent.decorator.ts @@ -0,0 +1,9 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import { Request } from 'express'; + +export const UserAgentDecorator = createParamDecorator( + (_data: unknown, context: ExecutionContext) => { + const request: Request = context.switchToHttp().getRequest(); + return request.headers['user-agent']; + }, +); diff --git a/src/common/filters/all-exceptions.filter.ts b/src/common/filters/all-exceptions.filter.ts new file mode 100644 index 0000000..4e57242 --- /dev/null +++ b/src/common/filters/all-exceptions.filter.ts @@ -0,0 +1,34 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, + Logger, +} from '@nestjs/common'; +import { Request, Response } from 'express'; + +@Catch() +export class AllExceptionsFilter implements ExceptionFilter { + private readonly logger = new Logger(AllExceptionsFilter.name); + catch(exception: any, host: ArgumentsHost): any { + const context = host.switchToHttp(); + const response: Response = context.getResponse(); + const request: Request = context.getRequest(); + + const status = + exception instanceof HttpException ? exception.getStatus() : 500; + const message = + exception instanceof HttpException + ? exception.message + : 'Internal server error'; + + this.logger.error(message, exception); + + response.status(status).json({ + status, + message, + timestamp: new Date().toISOString(), + path: request.baseUrl, + }); + } +} diff --git a/src/common/guards/auth.guard.ts b/src/common/guards/auth.guard.ts new file mode 100644 index 0000000..29fabfa --- /dev/null +++ b/src/common/guards/auth.guard.ts @@ -0,0 +1,24 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnauthorizedException, +} from '@nestjs/common'; +import { Request } from 'express'; +import { Observable } from 'rxjs'; + +@Injectable() +export class AuthGuard implements CanActivate { + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + const request: Request = context.switchToHttp().getRequest(); + const token = request.headers.authorization; + + if (!token || !token.startsWith('Bearer')) { + throw new UnauthorizedException('You are not authorized'); + } + + return true; + } +} diff --git a/src/common/interceptors/response.interceptor.ts b/src/common/interceptors/response.interceptor.ts new file mode 100644 index 0000000..e709e00 --- /dev/null +++ b/src/common/interceptors/response.interceptor.ts @@ -0,0 +1,22 @@ +import { + CallHandler, + ExecutionContext, + Injectable, + NestInterceptor, +} from '@nestjs/common'; +import { map, Observable } from 'rxjs'; + +@Injectable() +export class ResponseInterceptor implements NestInterceptor { + intercept( + context: ExecutionContext, + next: CallHandler, + ): Observable { + return next.handle().pipe( + map((data) => ({ + status: 'OK', + data, + })), + ); + } +} diff --git a/src/common/pipes/string-to-lowercase.pipe.ts b/src/common/pipes/string-to-lowercase.pipe.ts new file mode 100644 index 0000000..3b59f14 --- /dev/null +++ b/src/common/pipes/string-to-lowercase.pipe.ts @@ -0,0 +1,12 @@ +import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common'; + +@Injectable() +export class StringToLowercasePipe implements PipeTransform { + transform(value: any, metadata: ArgumentMetadata): any { + if (typeof value === 'string') { + return value.toLocaleLowerCase(); + } + + return value; + } +} diff --git a/src/main.ts b/src/main.ts index 4d40be5..deef6f2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,16 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ValidationPipe } from '@nestjs/common'; +import { AllExceptionsFilter } from './common/filters/all-exceptions.filter'; +import { ResponseInterceptor } from './common/interceptors/response.interceptor'; import { loggerMiddlewareFunc } from './common/middlewares/logger/logger.middleware'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalPipes(new ValidationPipe()); app.use(loggerMiddlewareFunc); + app.useGlobalInterceptors(new ResponseInterceptor()); + app.useGlobalFilters(new AllExceptionsFilter()); await app.listen(process.env.PORT ?? 3000); } bootstrap();