MVP реализации MoexVibe — NestJS бэкенд + React фронтенд + CI/CD #1
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