diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index f8a5ef9..7fb7b51 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -21,6 +21,7 @@ import { CredentialsDto } from './auth.dto'; import { AuthService } from './auth.service'; import { AccessPayload } from './auth.types'; import { JwtAuthGuard } from './jwt-auth.guard'; +import { ApiProblemResponses } from '../common/decorators/api-safe-error-response.decorator'; const REFRESH_COOKIE = 'refresh_token'; const COOKIE_PATH = '/api/v1/auth'; @@ -42,6 +43,7 @@ class AuthenticationDto { } @ApiTags('auth') +@ApiProblemResponses() @Controller('auth') export class AuthController { constructor(private readonly auth: AuthService) {} diff --git a/src/common/decorators/api-safe-error-response.decorator.ts b/src/common/decorators/api-safe-error-response.decorator.ts index c20a049..27db677 100644 --- a/src/common/decorators/api-safe-error-response.decorator.ts +++ b/src/common/decorators/api-safe-error-response.decorator.ts @@ -1,19 +1,43 @@ -import { ApiInternalServerErrorResponse, ApiProperty } from '@nestjs/swagger'; +import { applyDecorators } from '@nestjs/common'; +import { + ApiExtraModels, + ApiProperty, + ApiResponse, + getSchemaPath, +} from '@nestjs/swagger'; -export class SafeErrorResponseDto { - @ApiProperty({ example: 500 }) - statusCode: number; +export class ProblemDetailsDto { + @ApiProperty({ example: 401 }) + status: number; - @ApiProperty({ example: 'Internal server error' }) - message: string; + @ApiProperty({ example: 'UNAUTHORIZED' }) + code: string; - @ApiProperty({ example: '/api/v1' }) - path: string; + @ApiProperty({ + required: false, + example: [{ pointer: '#/email', detail: 'must be a valid email address' }], + }) + errors?: { pointer: string; detail: string }[]; } -export function ApiSafeErrorResponse(): MethodDecorator & ClassDecorator { - return ApiInternalServerErrorResponse({ - description: 'Internal server error', - type: SafeErrorResponseDto, +function problemResponse(status: number, description: string) { + return ApiResponse({ + status, + description, + content: { + 'application/problem+json': { + schema: { $ref: getSchemaPath(ProblemDetailsDto) }, + }, + }, }); } + +export function ApiProblemResponses(): MethodDecorator & ClassDecorator { + return applyDecorators( + ApiExtraModels(ProblemDetailsDto), + problemResponse(400, 'Invalid request'), + problemResponse(401, 'Authentication required'), + problemResponse(404, 'Resource not found'), + problemResponse(500, 'Internal server error'), + ); +} diff --git a/src/tbank/brokerage-accounts.controller.ts b/src/tbank/brokerage-accounts.controller.ts index 75fc99e..60786bd 100644 --- a/src/tbank/brokerage-accounts.controller.ts +++ b/src/tbank/brokerage-accounts.controller.ts @@ -3,10 +3,12 @@ import { ApiBearerAuth, ApiOkResponse, ApiTags } from '@nestjs/swagger'; import type { Request } from 'express'; import { AccessPayload } from '../auth/auth.types'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { ApiProblemResponses } from '../common/decorators/api-safe-error-response.decorator'; import { BrokerageAccountDto } from './brokerage-account.dto'; import { BrokerageAccountsService } from './brokerage-accounts.service'; @ApiTags('brokerage-accounts') +@ApiProblemResponses() @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Controller('brokerage-accounts') diff --git a/src/tbank/portfolio.controller.ts b/src/tbank/portfolio.controller.ts index c951ee0..6dc3d22 100644 --- a/src/tbank/portfolio.controller.ts +++ b/src/tbank/portfolio.controller.ts @@ -3,10 +3,12 @@ import { ApiBearerAuth, ApiOkResponse, ApiTags } from '@nestjs/swagger'; import type { Request } from 'express'; import { AccessPayload } from '../auth/auth.types'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { ApiProblemResponses } from '../common/decorators/api-safe-error-response.decorator'; import { PortfolioDto } from './portfolio.dto'; import { PortfolioService } from './portfolio.service'; @ApiTags('brokerage-accounts') +@ApiProblemResponses() @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Controller('brokerage-accounts') diff --git a/src/tbank/tbank-connection.controller.ts b/src/tbank/tbank-connection.controller.ts index 8fcaab7..b780b37 100644 --- a/src/tbank/tbank-connection.controller.ts +++ b/src/tbank/tbank-connection.controller.ts @@ -17,6 +17,7 @@ import { import type { Request } from 'express'; import { AccessPayload } from '../auth/auth.types'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { ApiProblemResponses } from '../common/decorators/api-safe-error-response.decorator'; import { PutTbankConnectionDto, TbankConnectionStatusDto, @@ -24,6 +25,7 @@ import { import { TbankConnectionService } from './tbank-connection.service'; @ApiTags('tbank-connection') +@ApiProblemResponses() @ApiBearerAuth() @UseGuards(JwtAuthGuard) @Controller('tbank-connection')