106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
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 {
|
|
type 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<TbankSyncStatusDto> {
|
|
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 = saved.work.intent as unknown as ScreenIntent;
|
|
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<Awaited<ReturnType<TbankSyncRepository['read']>>>['work'],
|
|
): Promise<TbankSyncStatusDto> {
|
|
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,
|
|
};
|
|
}
|
|
}
|