refactor(api): describe bank operation create body as oneOf by kind (WealthLedger/backend#172) #173

Merged
codex-bot merged 1 commits from refactor/172-bank-operation-oneof into develop 2026-11-04 17:47:37 +03:00
2 changed files with 58 additions and 0 deletions
Showing only changes of commit 55054295fd - Show all commits
@@ -1,6 +1,7 @@
import {
ApiProperty,
ApiPropertyOptional,
getSchemaPath,
IntersectionType,
OmitType,
PartialType,
@@ -153,6 +154,49 @@ export class CreateBankOperationDto {
@MaxLength(MAX_OPERATION_DESCRIPTION_LENGTH)
description?: string;
}
// OpenAPI-only variants. Runtime validation stays on CreateBankOperationDto so
// invalid combinations keep the existing BANK_INVALID_INPUT response.
class BankOperationCreateBaseDto extends OmitType(CreateBankOperationDto, [
'kind', 'targetCardId', 'categoryId', 'originalExpenseId',
] as const) {}
export class CreateExpenseOperationDto extends BankOperationCreateBaseDto {
@ApiProperty({ enum: [BankOperationKind.Expense] }) declare kind: 'expense';
@ApiPropertyOptional({ format: 'uuid' }) declare categoryId?: string;
}
export class CreateTopupOperationDto extends BankOperationCreateBaseDto {
@ApiProperty({ enum: [BankOperationKind.Topup] }) declare kind: 'topup';
}
export class CreateTransferOperationDto extends BankOperationCreateBaseDto {
@ApiProperty({ enum: [BankOperationKind.Transfer] }) declare kind: 'transfer';
@ApiProperty({ format: 'uuid' }) declare targetCardId: string;
}
export class CreateRefundOperationDto extends BankOperationCreateBaseDto {
@ApiProperty({ enum: [BankOperationKind.Refund] }) declare kind: 'refund';
@ApiProperty({ format: 'uuid' }) declare originalExpenseId: string;
}
export const createBankOperationSchema = {
oneOf: [
CreateExpenseOperationDto,
CreateTopupOperationDto,
CreateTransferOperationDto,
CreateRefundOperationDto,
].map((model) => ({ $ref: getSchemaPath(model) })),
discriminator: {
propertyName: 'kind',
mapping: {
expense: getSchemaPath(CreateExpenseOperationDto),
topup: getSchemaPath(CreateTopupOperationDto),
transfer: getSchemaPath(CreateTransferOperationDto),
refund: getSchemaPath(CreateRefundOperationDto),
},
},
};
export class UpdateBankOperationDto extends PartialType(
OmitType(CreateBankOperationDto, ['id'] as const),
{ skipNullProperties: false },
@@ -16,7 +16,9 @@ import {
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiBody,
ApiCreatedResponse,
ApiExtraModels,
ApiNoContentResponse,
ApiOkResponse,
ApiResponse,
@@ -46,6 +48,11 @@ import {
CategoryNameDto,
CreateBankCardDto,
CreateBankOperationDto,
createBankOperationSchema,
CreateExpenseOperationDto,
CreateRefundOperationDto,
CreateTopupOperationDto,
CreateTransferOperationDto,
ExpenseCategoryDto,
UpdateBankCardDto,
UpdateBankOperationDto,
@@ -73,6 +80,12 @@ type AuthRequest = Request & { user: AccessPayload };
},
})
@ApiTags('bank-cards')
@ApiExtraModels(
CreateExpenseOperationDto,
CreateTopupOperationDto,
CreateTransferOperationDto,
CreateRefundOperationDto,
)
@ApiBearerAuth()
@ApiProblemResponses()
@UseGuards(JwtAuthGuard)
@@ -131,6 +144,7 @@ export class BankCardsController {
}
@Post('operations')
@ApiBody({ schema: createBankOperationSchema })
@ApiCreatedResponse({ type: BankOperationDto })
createOperation(
@Req() req: AuthRequest,