import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { Environment } from '../config/env.validation'; import { PrismaService } from '../prisma/prisma.service'; import { TbankClient } from './tbank.client'; import { TbankCrypto } from './tbank.crypto'; import { PortfolioService } from './portfolio.service'; type TbankTokenValidator = Pick; @Injectable() export class TbankConnectionService { private readonly crypto: TbankCrypto; constructor( private readonly prisma: PrismaService, config: ConfigService, @Inject(TbankClient) private readonly tbank: TbankTokenValidator, private readonly portfolio: PortfolioService, ) { this.crypto = TbankCrypto.fromBase64( config.getOrThrow('TOKEN_ENCRYPTION_KEY'), ); } async status(userId: string) { const connection = await this.prisma.tbankConnection.findUnique({ where: { userId }, }); return { connected: Boolean(connection), connectedAt: connection?.connectedAt.toISOString() ?? null, }; } async put(userId: string, value: string) { const token = value.trim(); if (!token) throw new BadRequestException({ code: 'VALIDATION_ERROR' }); try { await this.tbank.validateToken(token); } catch (error) { if (error instanceof Error && error.message === 'INVALID_TBANK_TOKEN') { throw new BadRequestException({ code: 'INVALID_TBANK_TOKEN' }); } throw error; } const connection = await this.prisma.tbankConnection.upsert({ where: { userId }, create: { userId, encryptedToken: this.crypto.encrypt(token), connectedAt: new Date(), }, update: { encryptedToken: this.crypto.encrypt(token), connectedAt: new Date(), }, }); this.portfolio.invalidate(userId); return { connected: true, connectedAt: connection.connectedAt.toISOString(), }; } async delete(userId: string): Promise { await this.prisma.tbankConnection.deleteMany({ where: { userId } }); this.portfolio.invalidate(userId); } }