refactor(tbank): centralize access rules

This commit is contained in:
2026-08-25 12:06:41 +03:00
parent 5375e5598a
commit 130b303ec1
18 changed files with 573 additions and 327 deletions
+6 -45
View File
@@ -1,14 +1,6 @@
import {
BadRequestException,
ConflictException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Injectable } from '@nestjs/common';
import { LRUCache } from 'lru-cache';
import { RequestContextService } from '../common/logging/request-context.service';
import { Environment } from '../config/env.validation';
import { PrismaService } from '../prisma/prisma.service';
import {
BrokerageOperationsPageDto,
BrokerageOperationsQueryDto,
@@ -17,21 +9,14 @@ import {
mapBrokerageOperation,
operationCategory,
} from './brokerage-operation.mapper';
import { TbankAccessService } from './tbank-access.service';
import { TbankClient } from './tbank.client';
import { TbankCrypto } from './tbank.crypto';
const freshForMs = 30_000;
const maxCachedPages = 100;
const supportedAccounts = new Set([
'ACCOUNT_TYPE_TINKOFF',
'ACCOUNT_TYPE_TINKOFF_IIS',
'ACCOUNT_TYPE_INVEST_BOX',
'ACCOUNT_TYPE_INVEST_FUND',
]);
@Injectable()
export class BrokerageOperationsService {
private readonly crypto: TbankCrypto;
private readonly pages = new LRUCache<string, BrokerageOperationsPageDto>({
max: maxCachedPages,
ttl: freshForMs,
@@ -39,15 +24,10 @@ export class BrokerageOperationsService {
});
constructor(
private readonly prisma: PrismaService,
config: ConfigService<Environment, true>,
private readonly access: TbankAccessService,
private readonly tbank: TbankClient,
private readonly requestContext: RequestContextService,
) {
this.crypto = TbankCrypto.fromBase64(
config.getOrThrow('TOKEN_ENCRYPTION_KEY'),
);
}
) {}
async get(
userId: string,
@@ -57,22 +37,7 @@ export class BrokerageOperationsService {
this.requestContext.set({ userId, brokerageAccountId: accountId });
const range = this.range(query);
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();
return this.access.withAccount(userId, accountId, async ({ token }) => {
const key = cacheKey(userId, accountId, query, range);
const cached = this.pages.get(key);
if (cached) return cached;
@@ -106,11 +71,7 @@ export class BrokerageOperationsService {
};
this.pages.set(key, page);
return page;
} catch (error) {
if (error instanceof Error && error.message === 'INVALID_TBANK_TOKEN')
throw new BadRequestException({ code: 'INVALID_TBANK_TOKEN' });
throw error;
}
});
}
private range(query: BrokerageOperationsQueryDto) {