import { Injectable } from '@nestjs/common'; import { createHash } from 'node:crypto'; import { investmentResourceMeta } from '../investment-resource.mapper'; import { PortfolioService } from '../portfolio/portfolio.api'; import { TbankSyncRepository } from '../tbank-sync/tbank-sync.repository'; import { BrokerageAccountDto, BrokerageAccountsResourceDto } from './brokerage-account.dto'; @Injectable() export class BrokerageAccountsService { constructor( private readonly portfolio: PortfolioService, private readonly sync: TbankSyncRepository, ) {} async list(userId: string): Promise { const saved = await this.portfolio.saved(userId, 'all'); const data: BrokerageAccountDto[] | null = saved.membership.completeness === 'incomplete' ? null : saved.accounts.map((account) => ({ id: account.id, name: account.name, type: account.type as BrokerageAccountDto['type'], openedOn: account.openedOn, portfolioSummary: account.snapshot ? { totalsByCurrency: account.snapshot.totalsByCurrency, stockPositionCount: account.snapshot.positions.filter( (position) => position.type === 'STOCK', ).length, bondPositionCount: account.snapshot.positions.filter( (position) => position.type === 'BOND', ).length, } : null, })); // The read-only result of the last hourly walk; never starts one. const hourly = await this.sync.hourly(userId); return { data, meta: investmentResourceMeta({ resourceKey: 'accounts', dataRevision: createHash('sha256') .update( JSON.stringify([ saved.membership.dataRevision, saved.accounts.map((account) => [ account.id, account.meta.dataRevision, ]), ]), ) .digest('base64url'), availability: data === null ? 'missing' : 'complete', fetchedAt: saved.membership.lastSuccessfulAt ? new Date(saved.membership.lastSuccessfulAt) : null, freshForMs: 3_600_000, work: null, dependencies: saved.accounts.map((account) => account.meta), }), sync: hourly ? { state: hourly.state, startedAt: hourly.startedAt.toISOString(), finishedAt: hourly.finishedAt?.toISOString() ?? null, problems: hourly.sections.map((section) => ({ ...section, accountId: section.accountId ?? null, })), } : null, }; } }