import { ConflictException, Injectable, NotFoundException, } from '@nestjs/common'; import { createHash } from 'node:crypto'; import { PrismaService } from '../../prisma/prisma.service'; import { SupportedTbankAccount, accessibleTbankAccount, supportedTbankAccounts, } from './tbank-account.policy'; import { TbankTokenService } from './tbank-token.service'; import { TbankAccount, TbankClient } from './tbank.client'; export type TbankConnectionContext = { token: string; generation: string; }; export type TbankAccountContext = TbankConnectionContext & { account: TbankAccount; }; @Injectable() export class TbankAccessService { constructor( private readonly prisma: PrismaService, private readonly tokens: TbankTokenService, private readonly tbank: TbankClient, ) {} async listSupportedAccounts( userId: string, ): Promise { return this.withSupportedAccounts(userId, ({ accounts }) => accounts); } async withSupportedAccounts( userId: string, handler: (context: { token: string; generation: string; accounts: SupportedTbankAccount[]; }) => Promise | T, ): Promise { return this.withConnection(userId, async ({ token, generation }) => handler({ token, generation, accounts: supportedTbankAccounts( await this.tbank.getOpenAccounts(token), ), }), ); } async withConnection( userId: string, handler: (context: TbankConnectionContext) => Promise | T, ): Promise { const connection = await this.connection(userId); return this.tokens.translateInvalidToken(async () => handler(connection)); } async withAccount( userId: string, accountId: string, handler: (context: TbankAccountContext) => Promise, ): Promise { return this.withConnection(userId, (connection) => this.withAccountForConnection(connection, accountId, handler), ); } async withAccountForConnection( connection: TbankConnectionContext, accountId: string, handler: (context: TbankAccountContext) => Promise, ): Promise { 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, generation, account }); }); } 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), generation: createHash('sha256') .update(connection.encryptedToken) .digest('base64url'), }; } }