fix(tbank): isolate caches by connection generation

This commit is contained in:
2026-09-01 05:55:01 +03:00
parent da17893350
commit 19fe7e75d5
10 changed files with 317 additions and 59 deletions
+38 -7
View File
@@ -3,6 +3,7 @@ import {
Injectable,
NotFoundException,
} from '@nestjs/common';
import { createHash } from 'node:crypto';
import { PrismaService } from '../prisma/prisma.service';
import { BrokerageAccountDto } from './brokerage-account.dto';
import {
@@ -12,8 +13,12 @@ import {
import { TbankClient, TbankAccount } from './tbank.client';
import { TbankTokenService } from './tbank-token.service';
export type TbankAccountContext = {
export type TbankConnectionContext = {
token: string;
generation: string;
};
export type TbankAccountContext = TbankConnectionContext & {
account: TbankAccount;
};
@@ -33,13 +38,14 @@ export class TbankAccessService {
userId: string,
handler: (context: {
token: string;
generation: string;
accounts: BrokerageAccountDto[];
}) => Promise<T> | T,
): Promise<T> {
const { token } = await this.connection(userId);
return this.tokens.translateInvalidToken(async () =>
return this.withConnection(userId, async ({ token, generation }) =>
handler({
token,
generation,
accounts: supportedTbankAccounts(
await this.tbank.getOpenAccounts(token),
),
@@ -47,28 +53,53 @@ export class TbankAccessService {
);
}
async withConnection<T>(
userId: string,
handler: (context: TbankConnectionContext) => Promise<T> | T,
): Promise<T> {
const connection = await this.connection(userId);
return this.tokens.translateInvalidToken(async () => handler(connection));
}
async withAccount<T>(
userId: string,
accountId: string,
handler: (context: TbankAccountContext) => Promise<T>,
): Promise<T> {
const { token } = await this.connection(userId);
return this.withConnection(userId, (connection) =>
this.withAccountForConnection(connection, accountId, handler),
);
}
async withAccountForConnection<T>(
connection: TbankConnectionContext,
accountId: string,
handler: (context: TbankAccountContext) => Promise<T>,
): Promise<T> {
const { token, generation } = connection;
return this.tokens.translateInvalidToken(async () => {
const account = accessibleTbankAccount(
await this.tbank.getOpenAccounts(token),
accountId,
);
if (!account) throw new NotFoundException();
return handler({ token, account });
return handler({ token, generation, account });
});
}
private async connection(userId: string): Promise<{ token: string }> {
private async connection(
userId: string,
): Promise<{ token: string; generation: string }> {
const connection = await this.prisma.tbankConnection.findUnique({
where: { userId },
});
if (!connection)
throw new ConflictException({ code: 'TBANK_NOT_CONNECTED' });
return { token: this.tokens.decrypt(connection.encryptedToken) };
return {
token: this.tokens.decrypt(connection.encryptedToken),
generation: createHash('sha256')
.update(connection.encryptedToken)
.digest('base64url'),
};
}
}