refactor: organize backend by business responsibility
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Transform, Type } from 'class-transformer';
|
||||
import {
|
||||
IsArray,
|
||||
IsDateString,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
Max,
|
||||
MaxLength,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { MoneyDto } from '../../../../common/money.dto';
|
||||
|
||||
import { AccountReferenceDto } from '../../account-reference.dto';
|
||||
|
||||
export const operationCategories = [
|
||||
'trade',
|
||||
'income',
|
||||
'tax',
|
||||
'fee',
|
||||
'transfer',
|
||||
'other',
|
||||
] as const;
|
||||
export type OperationCategory = (typeof operationCategories)[number];
|
||||
|
||||
export class BrokerageOperationsQueryDto {
|
||||
@ApiPropertyOptional({
|
||||
format: 'uuid',
|
||||
description:
|
||||
'Instrument UID. Without from, includes history since the earliest available account opening.',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsUUID()
|
||||
instrumentUid?: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'date-time' })
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
from?: string;
|
||||
|
||||
@ApiPropertyOptional({ format: 'date-time' })
|
||||
@IsOptional()
|
||||
@IsDateString()
|
||||
to?: string;
|
||||
|
||||
@ApiPropertyOptional({ enum: operationCategories, isArray: true })
|
||||
@IsOptional()
|
||||
@Transform(({ value }: { value: unknown }) =>
|
||||
typeof value === 'string' ? value.split(',').filter(Boolean) : value,
|
||||
)
|
||||
@IsArray()
|
||||
@IsEnum(operationCategories, { each: true })
|
||||
categories?: OperationCategory[];
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(4096)
|
||||
cursor?: string;
|
||||
|
||||
@ApiPropertyOptional({ minimum: 1, maximum: 1000, default: 100 })
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(1000)
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export class BrokerageOperationDto {
|
||||
@ApiPropertyOptional({ type: AccountReferenceDto })
|
||||
account?: AccountReferenceDto;
|
||||
|
||||
@ApiProperty()
|
||||
declare id: string;
|
||||
|
||||
@ApiProperty({ format: 'date-time' })
|
||||
declare occurredAt: string;
|
||||
|
||||
@ApiProperty()
|
||||
declare type: string;
|
||||
|
||||
@ApiProperty({ enum: operationCategories })
|
||||
declare category: OperationCategory;
|
||||
|
||||
@ApiProperty()
|
||||
declare description: string;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare figi: string | null;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare instrumentUid: string | null;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare ticker: string | null;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare instrumentType: string | null;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare logoName: string | null;
|
||||
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare payment: MoneyDto | null;
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare quantity: string | null;
|
||||
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare price: MoneyDto | null;
|
||||
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare accruedInterest: MoneyDto | null;
|
||||
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare commission: MoneyDto | null;
|
||||
}
|
||||
|
||||
export class BrokerageOperationsPageDto {
|
||||
@ApiProperty()
|
||||
declare accountId: string;
|
||||
|
||||
@ApiProperty({ type: BrokerageOperationDto, isArray: true })
|
||||
declare items: BrokerageOperationDto[];
|
||||
|
||||
@ApiProperty({ type: String, nullable: true })
|
||||
declare nextCursor: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
declare hasNext: boolean;
|
||||
|
||||
@ApiProperty({ format: 'date-time' })
|
||||
declare observedAt: string;
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
jest.mock('../../../../prisma/prisma.service', () => ({
|
||||
PrismaService: class PrismaService {},
|
||||
}));
|
||||
|
||||
import { BrokerageOperationsController } from './brokerage-operations.controller';
|
||||
|
||||
describe('BrokerageOperationsController', () => {
|
||||
it('passes the authenticated user, account, and validated query to the service', async () => {
|
||||
const operations = { get: jest.fn() };
|
||||
const controller = new BrokerageOperationsController(operations as never);
|
||||
const query = { limit: 10 };
|
||||
const request = { user: { sub: 'user' } };
|
||||
|
||||
await controller.get(request as never, 'account', query);
|
||||
|
||||
expect(operations.get).toHaveBeenCalledWith('user', 'account', query);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Controller, Get, Param, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import type { Request } from 'express';
|
||||
import { ApiProblemResponses } from '../../../../common/decorators/api-safe-error-response.decorator';
|
||||
import { AccessPayload, JwtAuthGuard } from '../../../auth/auth.api';
|
||||
import {
|
||||
BrokerageOperationsPageDto,
|
||||
BrokerageOperationsQueryDto,
|
||||
} from './brokerage-operation.dto';
|
||||
import { BrokerageOperationsService } from '../application/brokerage-operations.service';
|
||||
|
||||
@ApiTags('brokerage-accounts')
|
||||
@ApiProblemResponses()
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('brokerage-accounts')
|
||||
export class BrokerageOperationsController {
|
||||
constructor(private readonly operations: BrokerageOperationsService) {}
|
||||
|
||||
@Get('all/operations')
|
||||
@ApiOkResponse({ type: BrokerageOperationsPageDto })
|
||||
all(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Query() query: BrokerageOperationsQueryDto,
|
||||
) {
|
||||
return this.operations.getAll(request.user.sub, query);
|
||||
}
|
||||
|
||||
@Get(':accountId/operations')
|
||||
@ApiOkResponse({ type: BrokerageOperationsPageDto })
|
||||
get(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('accountId') accountId: string,
|
||||
@Query() query: BrokerageOperationsQueryDto,
|
||||
) {
|
||||
return this.operations.get(request.user.sub, accountId, query);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user