fix: harden auth and investment synchronization
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Successful in 11m9s
Conventional commits / commits (pull_request) Successful in 28s
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 10m38s
Backend security / security (push) Successful in 8s
Backend verify / verify (push) Successful in 11m9s
Conventional commits / commits (pull_request) Successful in 28s
Backend security / security (pull_request) Successful in 7s
Backend verify / verify (pull_request) Successful in 10m38s
Resolve A01-A10 and C01-C07 from the backend audit. Preserve auth failures, forced candle reconciliation and forecast freshness. Validate MOEX responses and nested HTTP errors; remove unused legacy paths. Verified: 209 unit tests, 262 SQLite e2e tests on Linux, OpenAPI and durability. Record the remaining macOS native SQLite abort in the audit.
This commit is contained in:
@@ -10,7 +10,6 @@ import {
|
||||
Req,
|
||||
UseGuards,
|
||||
ValidationPipe,
|
||||
type ValidationError,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiAcceptedResponse,
|
||||
@@ -58,8 +57,8 @@ export class TbankSyncController {
|
||||
transform: true,
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
exceptionFactory: (message) =>
|
||||
new BadRequestException({ message: descriptorErrors(message) }),
|
||||
validationError: { target: false, value: false },
|
||||
exceptionFactory: (message) => new BadRequestException({ message }),
|
||||
}),
|
||||
)
|
||||
input: unknown,
|
||||
@@ -75,13 +74,3 @@ export class TbankSyncController {
|
||||
return this.sync.status(request.user.sub, id);
|
||||
}
|
||||
}
|
||||
|
||||
function descriptorErrors(
|
||||
errors: ValidationError[],
|
||||
prefix = '',
|
||||
): ValidationError[] {
|
||||
return errors.flatMap((error) => [
|
||||
{ ...error, property: `${prefix}${error.property}` },
|
||||
...descriptorErrors(error.children ?? [], `${prefix}${error.property}/`),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import type { Prisma } from '../../../../generated/prisma/client';
|
||||
import { PrismaService } from '../../../prisma/prisma.service';
|
||||
import type { TbankSyncAttempt } from '../../../integrations/tbank/tbank-access.service';
|
||||
import type { TbankSyncAttempt } from '../../../integrations/tbank/tbank.api';
|
||||
import type { InvestmentScreenDto } from './tbank-sync.dto';
|
||||
export type ScreenIntent = { scope: InvestmentScreenDto; now: string };
|
||||
export type ScreenProgress = {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
TbankAccessService,
|
||||
TbankMembershipService,
|
||||
InstrumentMetadataService,
|
||||
type TbankSyncAttempt,
|
||||
} from '../../../integrations/tbank/tbank.api';
|
||||
import { PortfolioService } from '../portfolio/portfolio.api';
|
||||
import {
|
||||
@@ -30,7 +31,6 @@ import { IncomeAnalyticsService } from '../income/income-read.api';
|
||||
import { UpcomingEventsService } from '../upcoming-events/upcoming-events-read.api';
|
||||
import { planInvestmentScreen } from '../investment-sync-plan';
|
||||
import type { InvestmentResourceMetaDto } from '../investment-resource.dto';
|
||||
import type { TbankSyncAttempt } from '../../../integrations/tbank/tbank-access.service';
|
||||
import {
|
||||
TbankSyncRepository,
|
||||
type ScreenIntent,
|
||||
@@ -303,6 +303,7 @@ export class TbankSyncService {
|
||||
},
|
||||
now,
|
||||
true,
|
||||
0,
|
||||
),
|
||||
]);
|
||||
}
|
||||
@@ -327,10 +328,17 @@ export class TbankSyncService {
|
||||
return this.sourceStatus(userId, saved.work);
|
||||
const intent = saved.work.intent as unknown as ScreenIntent;
|
||||
await this.access.local(userId, intent.scope.accountId);
|
||||
const running = saved.children.some((child) => child.state === 'running');
|
||||
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 ??
|
||||
saved.children.find((child) => child.errorCode)?.errorCode ??
|
||||
children.find((child) => child.errorCode)?.errorCode ??
|
||||
null;
|
||||
const state =
|
||||
saved.work.processRunId !== this.runId &&
|
||||
@@ -340,7 +348,7 @@ export class TbankSyncService {
|
||||
? 'running'
|
||||
: failed
|
||||
? 'failed'
|
||||
: saved.children.some((child) => child.state === 'cancelled')
|
||||
: children.some((child) => child.state === 'cancelled')
|
||||
? 'cancelled'
|
||||
: 'succeeded';
|
||||
const resources = await this.observe(userId, intent.scope);
|
||||
@@ -355,9 +363,8 @@ export class TbankSyncService {
|
||||
state,
|
||||
errorCode: failed,
|
||||
progress: {
|
||||
completed: saved.children.filter((child) => child.state !== 'running')
|
||||
.length,
|
||||
total: saved.children.length,
|
||||
completed: children.filter((child) => child.state !== 'running').length,
|
||||
total: children.length,
|
||||
discovering: !saved.progress.dispatched,
|
||||
},
|
||||
resources,
|
||||
|
||||
Reference in New Issue
Block a user