feat: wire up broker analytics endpoint, cache config, and module registration

- Add analytics cache key to TBANK_CACHE_KEYS
- Add tbankAnalyticsTtl to cache configuration
- Register and export BrokerAnalyticsService in TBankModule
- Add BrokerAnalyticsEnvelopeDto to broker envelope DTOs
- Add GET /accounts/:accountId/analytics endpoint to TBankController
- Fix TBankController spec to pass new constructor dependency
This commit is contained in:
Sergey Krylov 2026-06-24 09:41:57 +03:00
parent 5184d34262
commit 5cd56b239a
6 changed files with 29 additions and 3 deletions

View File

@ -35,6 +35,7 @@ export default registerAs('app', () => ({
tbankOperationsTtl: parseInt(process.env.CACHE_TBANK_OPERATIONS_TTL || '300', 10),
tbankPositionsTtl: parseInt(process.env.CACHE_TBANK_POSITIONS_TTL || '60', 10),
tbankInstrumentTtl: parseInt(process.env.CACHE_TBANK_INSTRUMENT_TTL || '86400', 10),
tbankAnalyticsTtl: parseInt(process.env.CACHE_TBANK_ANALYTICS_TTL || '300', 10),
},
auth: {
jwtSecret: process.env.JWT_SECRET || 'dev-jwt-secret-change-in-production',

View File

@ -5,6 +5,7 @@ import { BrokerOperationSyncResponseDto } from './broker-operation-sync-query.dt
import { BrokerOperationsPageResponseDto } from './broker-operation-response.dto';
import { BrokerPositionsPageResponseDto } from './broker-positions-page-response.dto';
import { BrokerPortfolioResponseDto } from './broker-portfolio-response.dto';
import { BrokerAnalyticsDto } from './broker-analytics-response.dto';
export class BrokerResponseMetaDto {
@ApiProperty({ nullable: true })
@ -54,6 +55,14 @@ export class BrokerOperationSyncEnvelopeDto {
meta!: BrokerResponseMetaDto;
}
export class BrokerAnalyticsEnvelopeDto {
@ApiProperty({ type: BrokerAnalyticsDto })
data!: BrokerAnalyticsDto;
@ApiProperty({ type: BrokerResponseMetaDto })
meta!: BrokerResponseMetaDto;
}
export class BrokerEventsEnvelopeDto {
@ApiProperty({ type: BrokerEventsDataDto })
data!: BrokerEventsDataDto;

View File

@ -20,4 +20,5 @@ export const TBANK_CACHE_KEYS = {
operations: 'tbank:operations',
instrument: 'tbank:instrument',
events: 'tbank:events',
analytics: 'tbank:analytics',
} as const;

View File

@ -2,6 +2,7 @@ import { ROLES_KEY } from '../auth/decorators/roles.decorator';
import { ApiResponse } from '../../common/dto/api-response.dto';
import { TBankController } from './tbank.controller';
import { BrokerAccountsService } from './services/broker-accounts.service';
import { BrokerAnalyticsService } from './services/broker-analytics.service';
import { BrokerEventsService } from './services/broker-events.service';
import { BrokerOperationSyncService } from './services/broker-operation-sync.service';
import { BrokerOperationsService } from './services/broker-operations.service';
@ -9,6 +10,7 @@ import { BrokerPortfolioService } from './services/broker-portfolio.service';
describe('TBankController', () => {
const accounts = { findAll: vi.fn() } as unknown as BrokerAccountsService;
const analytics = { getAnalytics: vi.fn() } as unknown as BrokerAnalyticsService;
const portfolio = { getPortfolio: vi.fn() } as unknown as BrokerPortfolioService;
const events = { getEvents: vi.fn() } as unknown as BrokerEventsService;
const operations = { getOperations: vi.fn() } as unknown as BrokerOperationsService;
@ -37,7 +39,7 @@ describe('TBankController', () => {
meta: { fromCache: true, cachedAt: '2026-06-17T00:00:00.000Z' },
});
const controller = new TBankController(accounts, portfolio, events, operations, sync);
const controller = new TBankController(accounts, portfolio, events, operations, sync, analytics);
const response = await controller.getAccounts();
expect(response).toBeInstanceOf(ApiResponse);
@ -48,7 +50,7 @@ describe('TBankController', () => {
it('exposes a sync trigger for durable operation history', async () => {
vi.mocked(sync.syncAccount).mockResolvedValueOnce({ upserted: 2 });
const controller = new TBankController(accounts, portfolio, events, operations, sync);
const controller = new TBankController(accounts, portfolio, events, operations, sync, analytics);
const response = await controller.syncOperations('acc-1', {
from: '2026-06-01T00:00:00.000Z',
to: '2026-06-17T00:00:00.000Z',
@ -84,7 +86,7 @@ describe('TBankController', () => {
meta: { fromCache: false, cachedAt: '2026-06-22T00:00:00.000Z' },
});
const controller = new TBankController(accounts, portfolio, events, operations, sync);
const controller = new TBankController(accounts, portfolio, events, operations, sync, analytics);
const query = { from: '2026-06-22', to: '2026-07-29', types: 'dividend,coupon' };
const response = await controller.getEvents('acc-1', query);

View File

@ -4,6 +4,7 @@ import { ApiResponse } from '../../common/dto/api-response.dto';
import { Roles } from '../auth/decorators/roles.decorator';
import {
BrokerAccountsEnvelopeDto,
BrokerAnalyticsEnvelopeDto,
BrokerEventsEnvelopeDto,
BrokerOperationSyncEnvelopeDto,
BrokerOperationsEnvelopeDto,
@ -18,6 +19,7 @@ import { BrokerAccountsService } from './services/broker-accounts.service';
import { BrokerEventsService } from './services/broker-events.service';
import { BrokerOperationSyncService } from './services/broker-operation-sync.service';
import { BrokerOperationsService } from './services/broker-operations.service';
import { BrokerAnalyticsService } from './services/broker-analytics.service';
import { BrokerPortfolioService } from './services/broker-portfolio.service';
@ApiTags('Broker')
@ -31,6 +33,7 @@ export class TBankController {
private readonly brokerEventsService: BrokerEventsService,
private readonly brokerOperationsService: BrokerOperationsService,
private readonly brokerOperationSyncService: BrokerOperationSyncService,
private readonly brokerAnalyticsService: BrokerAnalyticsService,
) {}
@Get('accounts')
@ -84,6 +87,14 @@ export class TBankController {
return new ApiResponse(result.data, result.meta.fromCache, result.meta.cachedAt);
}
@Get('accounts/:accountId/analytics')
@ApiOperation({ summary: 'Get broker account profitability analytics' })
@ApiOkResponse({ type: BrokerAnalyticsEnvelopeDto })
async getAnalytics(@Param('accountId') accountId: string) {
const result = await this.brokerAnalyticsService.getAnalytics(accountId);
return new ApiResponse(result.data, result.meta.fromCache, result.meta.cachedAt);
}
@Post('accounts/:accountId/operations/sync')
@ApiOperation({ summary: 'Synchronize T-Bank broker account operations into local history' })
@ApiOkResponse({ type: BrokerOperationSyncEnvelopeDto })

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { MoexClientModule } from '../moex-client/moex-client.module';
import { TBankController } from './tbank.controller';
import { BrokerAccountsService } from './services/broker-accounts.service';
import { BrokerAnalyticsService } from './services/broker-analytics.service';
import { BrokerInstrumentsService } from './services/broker-instruments.service';
import { BrokerEventsService } from './services/broker-events.service';
import { BrokerOperationSyncService } from './services/broker-operation-sync.service';
@ -20,6 +21,7 @@ import { TBankClientService } from './services/tbank-client.service';
BrokerEventsService,
BrokerOperationsService,
BrokerOperationSyncService,
BrokerAnalyticsService,
],
exports: [
TBankClientService,