feat: add common DTOs, exception filter, transform interceptor, logging middleware
This commit is contained in:
parent
aac49cd0df
commit
5dcc1e12be
24
apps/backend/src/common/dto/api-response.dto.ts
Normal file
24
apps/backend/src/common/dto/api-response.dto.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class ApiResponseMeta {
|
||||||
|
@ApiProperty({ nullable: true })
|
||||||
|
cachedAt: string | null;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
fromCache: boolean;
|
||||||
|
|
||||||
|
constructor(fromCache: boolean, cachedAt: string | null = null) {
|
||||||
|
this.fromCache = fromCache;
|
||||||
|
this.cachedAt = cachedAt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApiResponse<T> {
|
||||||
|
data: T;
|
||||||
|
meta: ApiResponseMeta;
|
||||||
|
|
||||||
|
constructor(data: T, fromCache = false, cachedAt: string | null = null) {
|
||||||
|
this.data = data;
|
||||||
|
this.meta = new ApiResponseMeta(fromCache, cachedAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
apps/backend/src/common/dto/pagination.dto.ts
Normal file
20
apps/backend/src/common/dto/pagination.dto.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { Type } from 'class-transformer';
|
||||||
|
import { IsOptional, IsInt, Min, Max } from 'class-validator';
|
||||||
|
|
||||||
|
export class PaginationDto {
|
||||||
|
@ApiPropertyOptional({ default: 1 })
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
page?: number = 1;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({ default: 20 })
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
@Max(100)
|
||||||
|
limit?: number = 20;
|
||||||
|
}
|
||||||
44
apps/backend/src/common/filters/http-exception.filter.ts
Normal file
44
apps/backend/src/common/filters/http-exception.filter.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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<Response>();
|
||||||
|
const request = ctx.getRequest<Request>();
|
||||||
|
|
||||||
|
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||||
|
let message = 'Internal server error';
|
||||||
|
let error = 'Internal Server Error';
|
||||||
|
|
||||||
|
if (exception instanceof HttpException) {
|
||||||
|
status = exception.getStatus();
|
||||||
|
const res = exception.getResponse();
|
||||||
|
if (typeof res === 'string') {
|
||||||
|
message = res;
|
||||||
|
error = exception.name;
|
||||||
|
} else if (typeof res === 'object') {
|
||||||
|
const r = res as Record<string, unknown>;
|
||||||
|
message = (r.message as string) || message;
|
||||||
|
error = (r.error as string) || exception.name;
|
||||||
|
}
|
||||||
|
} else if (exception instanceof Error) {
|
||||||
|
message = exception.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
response.status(status).json({
|
||||||
|
statusCode: status,
|
||||||
|
message,
|
||||||
|
error,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
path: request.url,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NestInterceptor,
|
||||||
|
ExecutionContext,
|
||||||
|
CallHandler,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
import { ApiResponse } from '../dto/api-response.dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransformInterceptor<T>
|
||||||
|
implements NestInterceptor<T, ApiResponse<T>>
|
||||||
|
{
|
||||||
|
intercept(
|
||||||
|
context: ExecutionContext,
|
||||||
|
next: CallHandler,
|
||||||
|
): Observable<ApiResponse<T>> {
|
||||||
|
return next.handle().pipe(
|
||||||
|
map((data) => {
|
||||||
|
if (data instanceof ApiResponse) return data;
|
||||||
|
return new ApiResponse(data, false, null);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
|
||||||
|
import { Request, Response, NextFunction } from 'express';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RequestLoggingMiddleware implements NestMiddleware {
|
||||||
|
private logger = new Logger('HTTP');
|
||||||
|
|
||||||
|
use(req: Request, res: Response, next: NextFunction): void {
|
||||||
|
const { method, originalUrl } = req;
|
||||||
|
const start = Date.now();
|
||||||
|
|
||||||
|
res.on('finish', () => {
|
||||||
|
const { statusCode } = res;
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
this.logger.log(`${method} ${originalUrl} ${statusCode} ${duration}ms`);
|
||||||
|
});
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user