feat!: persist T-Bank data and synchronize reads
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Failing after 10m45s
Conventional commits / commits (pull_request) Successful in 29s
Backend security / security (pull_request) Successful in 8s
Backend verify / verify (pull_request) Failing after 10m37s
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Failing after 10m45s
Conventional commits / commits (pull_request) Successful in 29s
Backend security / security (pull_request) Successful in 8s
Backend verify / verify (pull_request) Failing after 10m37s
Store account membership, portfolio, operations, forecasts and market sources in SQLite. Coalesce scoped refreshes, fence connection revisions, preserve complete financial snapshots and stabilize operation period state. BREAKING CHANGE: investment reads return data/meta resource envelopes and locally persisted snapshots; consumers must use the matching OpenAPI contract and explicit synchronization endpoints.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { InvestmentReadQueryDto } from '../../../investment-resource.dto';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
@@ -15,15 +16,15 @@ import { GetStockCompanyUseCase } from '../application/get-stock-company.use-cas
|
||||
import { StockIncomeService } from '../application/stock-income.service';
|
||||
import { StockMarketService } from '../application/stock-market.service';
|
||||
import {
|
||||
StockCandlesDto,
|
||||
StockCandlesResourceDto,
|
||||
StockCandlesQueryDto,
|
||||
StockCompanyDto,
|
||||
StockDividendsDto,
|
||||
StockDto,
|
||||
StockPositionDto,
|
||||
StockQuoteDto,
|
||||
StockCompanyResourceDto,
|
||||
StockDividendsResourceDto,
|
||||
StockResourceDto,
|
||||
StockPositionResourceDto,
|
||||
StockQuoteResourceDto,
|
||||
StockSearchQueryDto,
|
||||
StockSearchResultDto,
|
||||
StockSearchResourceDto,
|
||||
StockTickerParamsDto,
|
||||
} from './stock.dto';
|
||||
import { StockCatalogService } from '../application/stock-catalog.service';
|
||||
@@ -41,68 +42,99 @@ export class StockController {
|
||||
private readonly companyInfo: GetStockCompanyUseCase,
|
||||
) {}
|
||||
@Get()
|
||||
@ApiOkResponse({ type: [StockSearchResultDto] })
|
||||
@ApiOkResponse({ type: StockSearchResourceDto })
|
||||
search(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Query() query: StockSearchQueryDto,
|
||||
) {
|
||||
return this.stocks.search(request.user.sub, query.query);
|
||||
return this.stocks.search(
|
||||
request.user.sub,
|
||||
query.query,
|
||||
query.sync === 'observe',
|
||||
);
|
||||
}
|
||||
@Get('by-ticker/:ticker')
|
||||
@ApiOkResponse({ type: StockDto })
|
||||
@ApiOkResponse({ type: StockResourceDto })
|
||||
getByTicker(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param() params: StockTickerParamsDto,
|
||||
@Query() query: InvestmentReadQueryDto,
|
||||
) {
|
||||
return this.stocks.getByTicker(request.user.sub, params.ticker);
|
||||
return this.stocks.getByTicker(
|
||||
request.user.sub,
|
||||
params.ticker,
|
||||
query.sync === 'observe',
|
||||
);
|
||||
}
|
||||
@Get(':uid')
|
||||
@ApiOkResponse({ type: StockDto })
|
||||
@ApiOkResponse({ type: StockResourceDto })
|
||||
get(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
@Query() query: InvestmentReadQueryDto,
|
||||
) {
|
||||
return this.stocks.get(request.user.sub, uid);
|
||||
return this.stocks.get(request.user.sub, uid, query.sync === 'observe');
|
||||
}
|
||||
@Get(':uid/quote')
|
||||
@ApiOkResponse({ type: StockQuoteDto })
|
||||
@ApiOkResponse({ type: StockQuoteResourceDto })
|
||||
quote(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
@Query() query: InvestmentReadQueryDto,
|
||||
) {
|
||||
return this.market.quote(request.user.sub, uid);
|
||||
return this.market.quote(request.user.sub, uid, query.sync === 'observe');
|
||||
}
|
||||
@Get(':uid/candles')
|
||||
@ApiOkResponse({ type: StockCandlesDto })
|
||||
@ApiOkResponse({ type: StockCandlesResourceDto })
|
||||
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);
|
||||
return this.market.candles(
|
||||
request.user.sub,
|
||||
uid,
|
||||
query.period,
|
||||
query.sync === 'observe',
|
||||
);
|
||||
}
|
||||
@Get(':uid/position')
|
||||
@ApiOkResponse({ type: StockPositionDto })
|
||||
@ApiOkResponse({ type: StockPositionResourceDto })
|
||||
position(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
@Query() query: InvestmentReadQueryDto,
|
||||
) {
|
||||
return this.income.position(request.user.sub, uid);
|
||||
return this.income.readPosition(
|
||||
request.user.sub,
|
||||
uid,
|
||||
query.sync === 'observe',
|
||||
);
|
||||
}
|
||||
@Get(':uid/company')
|
||||
@ApiOkResponse({ type: StockCompanyDto })
|
||||
@ApiOkResponse({ type: StockCompanyResourceDto })
|
||||
company(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
@Query() query: InvestmentReadQueryDto,
|
||||
) {
|
||||
return this.companyInfo.execute(request.user.sub, uid);
|
||||
return this.companyInfo.execute(
|
||||
request.user.sub,
|
||||
uid,
|
||||
query.sync === 'observe',
|
||||
);
|
||||
}
|
||||
@Get(':uid/dividends')
|
||||
@ApiOkResponse({ type: StockDividendsDto })
|
||||
@ApiOkResponse({ type: StockDividendsResourceDto })
|
||||
dividends(
|
||||
@Req() request: Request & { user: AccessPayload },
|
||||
@Param('uid', new ParseUUIDPipe()) uid: string,
|
||||
@Query() query: InvestmentReadQueryDto,
|
||||
) {
|
||||
return this.income.dividends(request.user.sub, uid);
|
||||
return this.income.dividends(
|
||||
request.user.sub,
|
||||
uid,
|
||||
query.sync === 'observe',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
import {
|
||||
InvestmentReadQueryDto,
|
||||
InvestmentResourceMetaDto,
|
||||
} from '../../../investment-resource.dto';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
import { Transform } from 'class-transformer';
|
||||
import {
|
||||
@@ -13,7 +17,7 @@ import { PortfolioPositionDto } from '../../../portfolio/portfolio.api';
|
||||
|
||||
const MAX_INSTRUMENT_QUERY_LENGTH = 80;
|
||||
|
||||
export class StockSearchQueryDto {
|
||||
export class StockSearchQueryDto extends InvestmentReadQueryDto {
|
||||
@ApiProperty({ minLength: 2, maxLength: MAX_INSTRUMENT_QUERY_LENGTH })
|
||||
@Transform(({ value }: { value: unknown }) =>
|
||||
typeof value === 'string' ? value.trim() : value,
|
||||
@@ -35,7 +39,7 @@ export class StockTickerParamsDto {
|
||||
@Matches(/^[A-Za-z0-9][A-Za-z0-9._@+-]*$/)
|
||||
declare ticker: string;
|
||||
}
|
||||
export class StockCandlesQueryDto {
|
||||
export class StockCandlesQueryDto extends InvestmentReadQueryDto {
|
||||
@ApiPropertyOptional({
|
||||
enum: ['month', 'halfYear', 'year', 'all'],
|
||||
default: 'month',
|
||||
@@ -168,3 +172,52 @@ export class StockDividendsDto {
|
||||
@ApiProperty() declare estimateUnavailable: boolean;
|
||||
@ApiProperty() declare positionIsStale: boolean;
|
||||
}
|
||||
|
||||
export class StockPositionResourceDto {
|
||||
@ApiProperty({ type: StockPositionDto, nullable: true })
|
||||
declare data: StockPositionDto | null;
|
||||
@ApiProperty({ type: InvestmentResourceMetaDto })
|
||||
declare meta: InvestmentResourceMetaDto;
|
||||
}
|
||||
|
||||
export class StockSearchResourceDto {
|
||||
@ApiProperty({ type: StockSearchResultDto, isArray: true, nullable: true })
|
||||
declare data: StockSearchResultDto[] | null;
|
||||
@ApiProperty({ type: InvestmentResourceMetaDto })
|
||||
declare meta: InvestmentResourceMetaDto;
|
||||
}
|
||||
|
||||
export class StockResourceDto {
|
||||
@ApiProperty({ type: StockDto, nullable: true })
|
||||
declare data: StockDto | null;
|
||||
@ApiProperty({ type: InvestmentResourceMetaDto })
|
||||
declare meta: InvestmentResourceMetaDto;
|
||||
}
|
||||
|
||||
export class StockQuoteResourceDto {
|
||||
@ApiProperty({ type: StockQuoteDto, nullable: true })
|
||||
declare data: StockQuoteDto | null;
|
||||
@ApiProperty({ type: InvestmentResourceMetaDto })
|
||||
declare meta: InvestmentResourceMetaDto;
|
||||
}
|
||||
|
||||
export class StockCompanyResourceDto {
|
||||
@ApiProperty({ type: StockCompanyDto, nullable: true })
|
||||
declare data: StockCompanyDto | null;
|
||||
@ApiProperty({ type: InvestmentResourceMetaDto })
|
||||
declare meta: InvestmentResourceMetaDto;
|
||||
}
|
||||
|
||||
export class StockCandlesResourceDto {
|
||||
@ApiProperty({ type: StockCandlesDto, nullable: true })
|
||||
declare data: StockCandlesDto | null;
|
||||
@ApiProperty({ type: InvestmentResourceMetaDto })
|
||||
declare meta: InvestmentResourceMetaDto;
|
||||
}
|
||||
|
||||
export class StockDividendsResourceDto {
|
||||
@ApiProperty({ type: StockDividendsDto, nullable: true })
|
||||
declare data: StockDividendsDto | null;
|
||||
@ApiProperty({ type: InvestmentResourceMetaDto })
|
||||
declare meta: InvestmentResourceMetaDto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user