refactor: inject TbankAccessService directly into stock screener (#151) #155
@@ -1,6 +1,5 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { TbankAccessService } from '../../../integrations/tbank/tbank-access.service';
|
|
||||||
import { TbankModule } from '../../../integrations/tbank/tbank.module';
|
import { TbankModule } from '../../../integrations/tbank/tbank.module';
|
||||||
import { AuthModule } from '../../auth/auth.module';
|
import { AuthModule } from '../../auth/auth.module';
|
||||||
import { PortfolioModule } from '../portfolio/portfolio.module';
|
import { PortfolioModule } from '../portfolio/portfolio.module';
|
||||||
@@ -21,7 +20,6 @@ import { StockController } from './stocks/presentation/stock.controller';
|
|||||||
controllers: [StockController, BondController],
|
controllers: [StockController, BondController],
|
||||||
exports: [StockIncomeService, BondIncomeService],
|
exports: [StockIncomeService, BondIncomeService],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: 'TBANK_ACCESS', useExisting: TbankAccessService },
|
|
||||||
BondIncomeService,
|
BondIncomeService,
|
||||||
BondMarketService,
|
BondMarketService,
|
||||||
GetStockCompanyUseCase,
|
GetStockCompanyUseCase,
|
||||||
|
|||||||
+5
-1
@@ -1,3 +1,7 @@
|
|||||||
|
jest.mock('../../../../../integrations/tbank/tbank.api', () => ({
|
||||||
|
TbankAccessService: class {},
|
||||||
|
}));
|
||||||
|
|
||||||
import { NotFoundException, ServiceUnavailableException } from '@nestjs/common';
|
import { NotFoundException, ServiceUnavailableException } from '@nestjs/common';
|
||||||
|
|
||||||
import type { TbankShare } from '../../../../../integrations/tbank/tbank.api';
|
import type { TbankShare } from '../../../../../integrations/tbank/tbank.api';
|
||||||
@@ -36,7 +40,7 @@ describe('StockScreenerService', () => {
|
|||||||
client.getAllShares.mockResolvedValue(
|
client.getAllShares.mockResolvedValue(
|
||||||
Array.from({ length: pageSize + 1 }, (_, index) => share(index)),
|
Array.from({ length: pageSize + 1 }, (_, index) => share(index)),
|
||||||
);
|
);
|
||||||
service = new StockScreenerService(access, client as never, logger as never);
|
service = new StockScreenerService(access as never, client as never, logger as never);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('stores the full selected catalog and returns stable non-overlapping pages', async () => {
|
it('stores the full selected catalog and returns stable non-overlapping pages', async () => {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
Inject,
|
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
ServiceUnavailableException,
|
ServiceUnavailableException,
|
||||||
@@ -10,6 +9,7 @@ import { randomUUID } from 'node:crypto';
|
|||||||
import type { TbankShare } from '../../../../../integrations/tbank/tbank-market.types';
|
import type { TbankShare } from '../../../../../integrations/tbank/tbank-market.types';
|
||||||
|
|
||||||
import { StructuredLogger } from '../../../../../common/logging/structured-logger.service';
|
import { StructuredLogger } from '../../../../../common/logging/structured-logger.service';
|
||||||
|
import { TbankAccessService } from '../../../../../integrations/tbank/tbank.api';
|
||||||
import { TbankClient } from '../../../../../integrations/tbank/tbank.client';
|
import { TbankClient } from '../../../../../integrations/tbank/tbank.client';
|
||||||
import {
|
import {
|
||||||
selectStockScreenerInstruments,
|
selectStockScreenerInstruments,
|
||||||
@@ -47,17 +47,13 @@ type StockScreenerPage = {
|
|||||||
searchId: string;
|
searchId: string;
|
||||||
};
|
};
|
||||||
type Snapshot = { asOf: string; expiresAt: number; items: StockScreenerItem[]; revision: string; userId: string };
|
type Snapshot = { asOf: string; expiresAt: number; items: StockScreenerItem[]; revision: string; userId: string };
|
||||||
type TbankAccess = {
|
|
||||||
local(userId: string): Promise<{ revision: string }>;
|
|
||||||
online<T>(userId: string, run: (connection: { revision: string; token: string }) => Promise<T>): Promise<T>;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StockScreenerService {
|
export class StockScreenerService {
|
||||||
private readonly snapshots = new Map<string, Snapshot>();
|
private readonly snapshots = new Map<string, Snapshot>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('TBANK_ACCESS') private readonly access: TbankAccess,
|
private readonly access: TbankAccessService,
|
||||||
private readonly client: TbankClient,
|
private readonly client: TbankClient,
|
||||||
private readonly logger: StructuredLogger,
|
private readonly logger: StructuredLogger,
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export class StockController {
|
|||||||
@Body(new ValidationPipe({ forbidNonWhitelisted: true, forbidUnknownValues: true, whitelist: true }))
|
@Body(new ValidationPipe({ forbidNonWhitelisted: true, forbidUnknownValues: true, whitelist: true }))
|
||||||
criteria: StockScreenerCriteriaDto,
|
criteria: StockScreenerCriteriaDto,
|
||||||
) {
|
) {
|
||||||
|
// The global whitelist pipe strips unknown keys before this param pipe runs, so only the raw body can reject them.
|
||||||
const allowed = new Set(['countryOfRisk', 'currency', 'maxLot', 'query', 'realExchange', 'sector', 'shareType']);
|
const allowed = new Set(['countryOfRisk', 'currency', 'maxLot', 'query', 'realExchange', 'sector', 'shareType']);
|
||||||
if (Object.keys(request.body as object).some((key) => !allowed.has(key))) {
|
if (Object.keys(request.body as object).some((key) => !allowed.has(key))) {
|
||||||
throw new BadRequestException();
|
throw new BadRequestException();
|
||||||
|
|||||||
Reference in New Issue
Block a user