refactor(tbank): centralize access rules
This commit is contained in:
@@ -1,15 +1,6 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
ServiceUnavailableException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { Environment } from '../config/env.validation';
|
||||
import { Injectable, ServiceUnavailableException } from '@nestjs/common';
|
||||
import { RequestContextService } from '../common/logging/request-context.service';
|
||||
import { StructuredLogger } from '../common/logging/structured-logger.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import {
|
||||
add,
|
||||
decimal,
|
||||
@@ -19,18 +10,12 @@ import {
|
||||
subtract,
|
||||
} from './decimal';
|
||||
import { MoneyDto, PortfolioDto, PortfolioPositionDto } from './portfolio.dto';
|
||||
import { TbankAccessService } from './tbank-access.service';
|
||||
import { TbankClient, TbankMoney } from './tbank.client';
|
||||
import { TbankCrypto } from './tbank.crypto';
|
||||
|
||||
const freshForMs = 30_000;
|
||||
const fallbackForMs = 300_000;
|
||||
const metadataForMs = 86_400_000;
|
||||
const supportedAccounts = new Set([
|
||||
'ACCOUNT_TYPE_TINKOFF',
|
||||
'ACCOUNT_TYPE_TINKOFF_IIS',
|
||||
'ACCOUNT_TYPE_INVEST_BOX',
|
||||
'ACCOUNT_TYPE_INVEST_FUND',
|
||||
]);
|
||||
const types: Record<string, string> = {
|
||||
share: 'STOCK',
|
||||
bond: 'BOND',
|
||||
@@ -55,21 +40,15 @@ type CachedMetadata = {
|
||||
|
||||
@Injectable()
|
||||
export class PortfolioService {
|
||||
private readonly crypto: TbankCrypto;
|
||||
private readonly snapshots = new Map<string, CachedSnapshot>();
|
||||
private readonly metadata = new Map<string, CachedMetadata>();
|
||||
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
config: ConfigService<Environment, true>,
|
||||
private readonly access: TbankAccessService,
|
||||
private readonly tbank: TbankClient,
|
||||
private readonly requestContext: RequestContextService,
|
||||
private readonly logger: StructuredLogger,
|
||||
) {
|
||||
this.crypto = TbankCrypto.fromBase64(
|
||||
config.getOrThrow('TOKEN_ENCRYPTION_KEY'),
|
||||
);
|
||||
}
|
||||
) {}
|
||||
|
||||
async get(userId: string, accountId: string): Promise<PortfolioDto> {
|
||||
this.requestContext.set({ userId, brokerageAccountId: accountId });
|
||||
@@ -78,36 +57,24 @@ export class PortfolioService {
|
||||
if (cached && Date.now() - cached.storedAt <= freshForMs)
|
||||
return cached.snapshot;
|
||||
try {
|
||||
const connection = await this.prisma.tbankConnection.findUnique({
|
||||
where: { userId },
|
||||
});
|
||||
if (!connection)
|
||||
throw new ConflictException({ code: 'TBANK_NOT_CONNECTED' });
|
||||
const token = this.crypto.decrypt(connection.encryptedToken);
|
||||
const accounts = await this.tbank.getOpenAccounts(token);
|
||||
if (
|
||||
!accounts.some(
|
||||
(account) =>
|
||||
account.id === accountId && supportedAccounts.has(account.type),
|
||||
)
|
||||
) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
const [portfolio, positions] = await Promise.all([
|
||||
this.tbank.getPortfolio(token, accountId),
|
||||
this.tbank.getPositions(token, accountId),
|
||||
]);
|
||||
const snapshot = await this.normalize(
|
||||
token,
|
||||
portfolio.positions ?? [],
|
||||
positions.money ?? [],
|
||||
const snapshot = await this.access.withAccount(
|
||||
userId,
|
||||
accountId,
|
||||
async ({ token }) => {
|
||||
const [portfolio, positions] = await Promise.all([
|
||||
this.tbank.getPortfolio(token, accountId),
|
||||
this.tbank.getPositions(token, accountId),
|
||||
]);
|
||||
return this.normalize(
|
||||
token,
|
||||
portfolio.positions ?? [],
|
||||
positions.money ?? [],
|
||||
);
|
||||
},
|
||||
);
|
||||
this.snapshots.set(key, { snapshot, storedAt: Date.now() });
|
||||
return snapshot;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'INVALID_TBANK_TOKEN') {
|
||||
throw new BadRequestException({ code: 'INVALID_TBANK_TOKEN' });
|
||||
}
|
||||
if (
|
||||
error instanceof ServiceUnavailableException &&
|
||||
cached &&
|
||||
|
||||
Reference in New Issue
Block a user