refactor: organize backend by business responsibility
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
ParseUUIDPipe,
|
||||
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 { GetStockCompanyUseCase } from '../application/get-stock-company.use-case';
|
||||
import { StockIncomeService } from '../application/stock-income.service';
|
||||
import { StockMarketService } from '../application/stock-market.service';
|
||||
import {
|
||||
StockCandlesDto,
|
||||
StockCandlesQueryDto,
|
||||
StockCompanyDto,
|
||||
StockDividendsDto,
|
||||
StockDto,
|
||||
StockPositionDto,
|
||||
StockQuoteDto,
|
||||
StockSearchQueryDto,
|
||||
StockSearchResultDto,
|
||||
StockTickerParamsDto,
|
||||
} from './stock.dto';
|
||||
import { StockCatalogService } from '../application/stock-catalog.service';
|
||||
|
||||
@ApiTags('stocks')
|
||||
@ApiProblemResponses()
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('stocks')
|
||||
export class StockController {
|
||||
constructor(
|
||||
private readonly stocks: StockCatalogService,
|
||||
private readonly market: StockMarketService,
|
||||
private readonly income: StockIncomeService,
|
||||
private readonly companyInfo: GetStockCompanyUseCase,
|
||||
) {}
|
||||
@Get()
|
||||
@ApiOkResponse({ type: [StockSearchResultDto] })
|
||||
search(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Query() query: StockSearchQueryDto,
|
||||
) {
|
||||
return this.stocks.search(request.user.sub, query.query);
|
||||
}
|
||||
@Get('by-ticker/:ticker')
|
||||
@ApiOkResponse({ type: StockDto })
|
||||
getByTicker(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param() params: StockTickerParamsDto,
|
||||
) {
|
||||
return this.stocks.getByTicker(request.user.sub, params.ticker);
|
||||
}
|
||||
@Get(':uid')
|
||||
@ApiOkResponse({ type: StockDto })
|
||||
get(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
) {
|
||||
return this.stocks.get(request.user.sub, uid);
|
||||
}
|
||||
@Get(':uid/quote')
|
||||
@ApiOkResponse({ type: StockQuoteDto })
|
||||
quote(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
) {
|
||||
return this.market.quote(request.user.sub, uid);
|
||||
}
|
||||
@Get(':uid/candles')
|
||||
@ApiOkResponse({ type: StockCandlesDto })
|
||||
candles(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
@Query() query: StockCandlesQueryDto,
|
||||
) {
|
||||
return this.market.candles(request.user.sub, uid, query.period);
|
||||
}
|
||||
@Get(':uid/position')
|
||||
@ApiOkResponse({ type: StockPositionDto })
|
||||
position(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
) {
|
||||
return this.income.position(request.user.sub, uid);
|
||||
}
|
||||
@Get(':uid/company')
|
||||
@ApiOkResponse({ type: StockCompanyDto })
|
||||
company(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
) {
|
||||
return this.companyInfo.execute(request.user.sub, uid);
|
||||
}
|
||||
@Get(':uid/dividends')
|
||||
@ApiOkResponse({ type: StockDividendsDto })
|
||||
dividends(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
) {
|
||||
return this.income.dividends(request.user.sub, uid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
IsIn,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Matches,
|
||||
MaxLength,
|
||||
MinLength,
|
||||
} from 'class-validator';
|
||||
import { MoneyDto } from '../../../../../common/money.dto';
|
||||
import { PortfolioPositionDto } from '../../../portfolio/portfolio.api';
|
||||
|
||||
export class StockSearchQueryDto {
|
||||
@ApiProperty({ minLength: 2, maxLength: 80 })
|
||||
@Transform(({ value }: { value: unknown }) =>
|
||||
typeof value === 'string' ? value.trim() : value,
|
||||
)
|
||||
@IsString()
|
||||
@MinLength(2)
|
||||
@MaxLength(80)
|
||||
declare query: string;
|
||||
}
|
||||
export class StockTickerParamsDto {
|
||||
@ApiProperty({
|
||||
minLength: 1,
|
||||
maxLength: 80,
|
||||
pattern: '^[A-Za-z0-9][A-Za-z0-9._@+-]*$',
|
||||
})
|
||||
@IsString()
|
||||
@MinLength(1)
|
||||
@MaxLength(80)
|
||||
@Matches(/^[A-Za-z0-9][A-Za-z0-9._@+-]*$/)
|
||||
declare ticker: string;
|
||||
}
|
||||
export class StockCandlesQueryDto {
|
||||
@ApiPropertyOptional({
|
||||
enum: ['month', 'halfYear', 'year', 'all'],
|
||||
default: 'month',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsIn(['month', 'halfYear', 'year', 'all'])
|
||||
period?: 'month' | 'halfYear' | 'year' | 'all';
|
||||
}
|
||||
export class StockDto {
|
||||
@ApiProperty({ format: 'uuid' }) declare instrumentUid: string;
|
||||
@ApiProperty({
|
||||
description: 'Unambiguous ticker URL segment, or the exact instrument UID.',
|
||||
})
|
||||
declare slug: string;
|
||||
@ApiProperty() declare name: string;
|
||||
@ApiProperty({ type: String, nullable: true }) declare isin: string | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare classCode:
|
||||
string | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare realExchange:
|
||||
string | null;
|
||||
@ApiProperty({ type: Boolean, nullable: true }) declare apiTradeAvailable:
|
||||
boolean | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare ticker: string | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare logoName:
|
||||
string | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare currency:
|
||||
string | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare shareType:
|
||||
string | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare sector: string | null;
|
||||
@ApiProperty({ type: Number, nullable: true }) declare lot: number | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date-time' })
|
||||
declare historyFrom: string | null;
|
||||
}
|
||||
export class StockSearchResultDto extends StockDto {
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare price: MoneyDto | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date-time' })
|
||||
declare priceAt: string | null;
|
||||
}
|
||||
export class StockQuoteDto {
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare price: MoneyDto | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date-time' })
|
||||
declare priceAt: string | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare tradingStatus:
|
||||
string | null;
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare change: MoneyDto | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare changePercent:
|
||||
string | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date' })
|
||||
declare previousCloseDate: string | null;
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare previousClose: MoneyDto | null;
|
||||
@ApiProperty({ type: [String] }) declare unavailable: string[];
|
||||
@ApiProperty({ format: 'date-time' }) declare observedAt: string;
|
||||
}
|
||||
export class StockCandleDto {
|
||||
@ApiProperty({ format: 'date-time' }) declare time: string;
|
||||
@ApiProperty() declare close: string;
|
||||
@ApiProperty() declare isComplete: boolean;
|
||||
}
|
||||
export class StockCandlesDto {
|
||||
@ApiProperty({ type: [StockCandleDto] }) declare candles: StockCandleDto[];
|
||||
@ApiProperty({ type: String, nullable: true }) declare currency:
|
||||
string | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date-time' })
|
||||
declare historyFrom: string | null;
|
||||
@ApiProperty({ format: 'date-time' }) declare observedAt: string;
|
||||
}
|
||||
export class StockPositionRowDto extends PortfolioPositionDto {
|
||||
@ApiProperty({ type: String, nullable: true }) declare returnPercent:
|
||||
string | null;
|
||||
}
|
||||
export class StockPositionDto {
|
||||
@ApiProperty() declare quantity: string;
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare currentValue: MoneyDto | null;
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare averagePurchasePrice: MoneyDto | null;
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare absoluteReturn: MoneyDto | null;
|
||||
@ApiProperty({ type: String, nullable: true }) declare returnPercent:
|
||||
string | null;
|
||||
@ApiProperty({ type: [StockPositionRowDto] })
|
||||
declare accounts: StockPositionRowDto[];
|
||||
@ApiProperty() declare isStale: boolean;
|
||||
@ApiProperty({ format: 'date-time' }) declare observedAt: string;
|
||||
}
|
||||
export class StockMetricDto {
|
||||
@ApiProperty() declare key: string;
|
||||
@ApiProperty() declare value: number;
|
||||
@ApiProperty({ enum: ['money', 'ratio', 'percent'] }) declare unit:
|
||||
'money' | 'ratio' | 'percent';
|
||||
@ApiProperty({ type: String, nullable: true }) declare currency:
|
||||
string | null;
|
||||
@ApiProperty({ type: String, nullable: true, enum: ['TTM', 'MRQ'] })
|
||||
declare period: 'TTM' | 'MRQ' | null;
|
||||
}
|
||||
export class StockCompanyDto {
|
||||
@ApiProperty({ type: String, nullable: true }) declare description:
|
||||
string | null;
|
||||
@ApiProperty({ type: [StockMetricDto] }) declare metrics: StockMetricDto[];
|
||||
@ApiProperty({ type: [String] }) declare unavailable: string[];
|
||||
@ApiProperty({ format: 'date-time' }) declare observedAt: string;
|
||||
}
|
||||
export class StockDividendDto {
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare perShare: MoneyDto | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date' })
|
||||
declare recordDate: string | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date' })
|
||||
declare lastBuyDate: string | null;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date' })
|
||||
declare paymentDate: string | null;
|
||||
@ApiProperty({ enum: ['upcoming', 'past', 'undated'] }) declare timing:
|
||||
'upcoming' | 'past' | 'undated';
|
||||
@ApiProperty({ type: MoneyDto, nullable: true })
|
||||
declare estimatedAmount: MoneyDto | null;
|
||||
}
|
||||
export class StockDividendsDto {
|
||||
@ApiProperty({ type: [StockDividendDto] })
|
||||
declare dividends: StockDividendDto[];
|
||||
@ApiProperty({ format: 'date' }) declare from: string;
|
||||
@ApiProperty({ format: 'date' }) declare to: string;
|
||||
@ApiProperty({ format: 'date-time' }) declare observedAt: string;
|
||||
@ApiProperty({ type: String, nullable: true, format: 'date-time' })
|
||||
declare positionAt: string | null;
|
||||
@ApiProperty() declare estimateUnavailable: boolean;
|
||||
@ApiProperty() declare positionIsStale: boolean;
|
||||
}
|
||||
Reference in New Issue
Block a user