import { ConflictException, Injectable, NotFoundException, } from '@nestjs/common'; import { randomUUID } from 'node:crypto'; import { TbankAccessService } from '../../../integrations/tbank/tbank.api'; import { BrokerageOperationsSyncService } from '../operations/operations.api'; import { TbankSyncStatusDto } from './tbank-sync.dto'; import { screenIntent, TbankSyncRepository } from './tbank-sync.repository'; @Injectable() export class TbankSyncService { private readonly runId = randomUUID(); private readonly startedAt = new Date(); constructor( private readonly repository: TbankSyncRepository, private readonly access: TbankAccessService, private readonly operations: BrokerageOperationsSyncService, ) {} async status(userId: string, id: string): Promise { const connection = await this.access .local(userId) .catch((error: unknown) => { if (error instanceof ConflictException) throw new NotFoundException(); throw error; }); const saved = await this.repository.read(userId, connection.revision, id); if (!saved) throw new NotFoundException(); if (!saved.work.scopeKey.startsWith('screen:')) { return this.sourceStatus(userId, saved.work); } const intent = screenIntent(saved.work.intent); await this.access.local(userId, intent.scope.accountId); const children = await Promise.all( saved.children.map((child) => child.state === 'superseded' ? this.operations.observe(userId, child.id) : Promise.resolve(child), ), ); const running = children.some((child) => child.state === 'running'); const failed = saved.work.errorCode ?? children.find((child) => child.errorCode)?.errorCode ?? null; const state = saved.work.processRunId !== this.runId && (!saved.progress.dispatched || running) ? 'cancelled' : !saved.progress.dispatched || running ? 'running' : failed ? 'failed' : children.some((child) => child.state === 'cancelled') ? 'cancelled' : 'succeeded'; if ( (await this.access.local(userId, intent.scope.accountId)).revision !== connection.revision ) { throw new NotFoundException(); } return { id, ...intent, state, errorCode: failed, }; } private async sourceStatus( userId: string, work: NonNullable>>['work'], ): Promise { const observed = work.scopeKey.startsWith('operations:') ? await this.operations.observe(userId, work.id) : work; const state = observed.state === 'running' ? observed.updatedAt < this.startedAt ? 'cancelled' : 'running' : observed.state === 'failed' ? 'failed' : observed.state === 'cancelled' ? 'cancelled' : 'succeeded'; if ((await this.access.local(userId)).revision !== work.revision) { throw new NotFoundException(); } return { id: work.id, scope: null, now: work.createdAt.toISOString(), state, errorCode: observed.errorCode, }; } }