refactor: enforce enum machine codes
Backend security / security (push) Successful in 8s
Backend security / security (pull_request) Canceled after 0s
Backend verify / verify (push) Canceled after 9s
Backend verify / verify (pull_request) Canceled after 4s
Conventional commits / commits (pull_request) Successful in 31s
Backend security / security (push) Successful in 8s
Backend security / security (pull_request) Canceled after 0s
Backend verify / verify (push) Canceled after 9s
Backend verify / verify (pull_request) Canceled after 4s
Conventional commits / commits (pull_request) Successful in 31s
Use owner enums for error and T-Bank protocol values with ESLint regression checks. Preserve open wire fallbacks and keep protocol imports independent of DI and Prisma. Verified npm ci, verify, 226 unit tests, 262 isolated e2e tests and unchanged OpenAPI.
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
formatDecimal,
|
||||
subtract,
|
||||
} from '../../../common/decimal';
|
||||
import { isTbankProtocolValue, TbankOperationType } from '../../../integrations/tbank/tbank-protocol';
|
||||
import { DateOnlyRange, moscowDate } from '../investment-calendar';
|
||||
import { investmentResourceMeta } from '../investment-resource.mapper';
|
||||
import {
|
||||
@@ -165,7 +166,7 @@ function qualifying(entries: BrokerageOperationDto[]): BrokerageOperationDto[] {
|
||||
return entries.filter(
|
||||
(entry) =>
|
||||
Boolean(entry.payment) &&
|
||||
['OPERATION_TYPE_COUPON', 'OPERATION_TYPE_DIVIDEND'].includes(entry.type),
|
||||
[TbankOperationType.Coupon, TbankOperationType.Dividend].some((type) => isTbankProtocolValue(entry.type, type)),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ function totals(entries: BrokerageOperationDto[]): IncomeCurrencySummaryDto[] {
|
||||
if (!entry.payment) continue;
|
||||
const total = values.get(entry.payment.currency) ?? zeroTotal();
|
||||
const value = decimalFromString(entry.payment.value);
|
||||
if (entry.type === 'OPERATION_TYPE_COUPON') {
|
||||
if (isTbankProtocolValue(entry.type, TbankOperationType.Coupon)) {
|
||||
total.coupon = add(total.coupon, value);
|
||||
} else total.dividend = add(total.dividend, value);
|
||||
values.set(entry.payment.currency, total);
|
||||
|
||||
@@ -8,6 +8,8 @@ import {
|
||||
utcDate,
|
||||
} from '../investment-calendar';
|
||||
|
||||
enum IncomeErrorCode { InvalidPeriod = 'INVALID_INCOME_PERIOD' }
|
||||
|
||||
const MONDAY_WEEKDAY_INDEX = 1;
|
||||
const INCLUSIVE_DAY_OFFSET = 1;
|
||||
const NEXT_DAY_OFFSET = 1;
|
||||
@@ -163,5 +165,5 @@ function yearAgo(range: DateOnlyRange): DateOnlyRange | null {
|
||||
}
|
||||
|
||||
function invalidPeriod(): BadRequestException {
|
||||
return new BadRequestException({ code: 'INVALID_INCOME_PERIOD' });
|
||||
return new BadRequestException({ code: IncomeErrorCode.InvalidPeriod });
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { formatDecimal } from '../../../../common/decimal';
|
||||
import { StructuredLogger } from '../../../../common/logging/structured-logger.service';
|
||||
import { MILLISECONDS_PER_DAY } from '../../../../common/time';
|
||||
import { decimal } from '../../../../integrations/tbank/decimal';
|
||||
import { isTbankErrorCode, TbankErrorCode } from '../../../../integrations/tbank/tbank-error-code';
|
||||
import {
|
||||
TbankAccessService,
|
||||
TbankClient,
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
candleWindows,
|
||||
utcDay,
|
||||
} from '../domain/candle-coverage';
|
||||
import { InstrumentErrorCode } from '../domain/instrument-error-code';
|
||||
import { CandleRepository } from '../infrastructure/candle.repository';
|
||||
import { CatalogEnrichmentService } from './catalog-enrichment.service';
|
||||
import { timestamp } from './instrument-market';
|
||||
@@ -71,8 +73,8 @@ function rangeFor(
|
||||
throw new ServiceUnavailableException({
|
||||
code:
|
||||
input.instrumentType === 'share'
|
||||
? 'STOCK_HISTORY_START_UNAVAILABLE'
|
||||
: 'BOND_HISTORY_START_UNAVAILABLE',
|
||||
? InstrumentErrorCode.StockHistoryStartUnavailable
|
||||
: InstrumentErrorCode.BondHistoryStartUnavailable,
|
||||
});
|
||||
}
|
||||
from.setTime(utcDay(new Date(history)).valueOf());
|
||||
@@ -448,7 +450,7 @@ export class CandleService {
|
||||
const producer = this.queue.shift();
|
||||
if (!producer) break;
|
||||
let success = false;
|
||||
let code = 'TBANK_UNAVAILABLE';
|
||||
let code: string = TbankErrorCode.Unavailable;
|
||||
try {
|
||||
const result = await this.load(producer);
|
||||
if (result === null) {
|
||||
@@ -457,7 +459,7 @@ export class CandleService {
|
||||
}
|
||||
success = result;
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'INVALID_TBANK_TOKEN') {
|
||||
if (error instanceof Error && isTbankErrorCode(error.message, TbankErrorCode.InvalidToken)) {
|
||||
await this.access.rejectMembershipToken(producer.attempt);
|
||||
}
|
||||
if (error instanceof ServiceUnavailableException) {
|
||||
@@ -523,7 +525,7 @@ export class CandleService {
|
||||
}
|
||||
} catch {
|
||||
this.logger.event('error', 'candles.persistence-failed', {
|
||||
code: 'TBANK_UNAVAILABLE',
|
||||
code: TbankErrorCode.Unavailable,
|
||||
});
|
||||
} finally {
|
||||
this.active.clear();
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { StructuredLogger } from '../../../../common/logging/structured-logger.service';
|
||||
import { isTbankErrorCode, TbankErrorCode } from '../../../../integrations/tbank/tbank-error-code';
|
||||
import {
|
||||
TbankAccessService,
|
||||
TbankClient,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
type TbankSyncAttempt,
|
||||
} from '../../../../integrations/tbank/tbank.api';
|
||||
import { investmentResourceMeta } from '../../investment-resource.mapper';
|
||||
import { InstrumentErrorCode } from '../domain/instrument-error-code';
|
||||
import {
|
||||
CatalogDiscoveryRepository,
|
||||
catalogQuery,
|
||||
@@ -209,8 +211,8 @@ export class CatalogDiscoveryService {
|
||||
throw new ConflictException({
|
||||
code:
|
||||
instrumentType === 'share'
|
||||
? 'STOCK_TICKER_AMBIGUOUS'
|
||||
: 'BOND_TICKER_AMBIGUOUS',
|
||||
? InstrumentErrorCode.StockTickerAmbiguous
|
||||
: InstrumentErrorCode.BondTickerAmbiguous,
|
||||
});
|
||||
}
|
||||
const result = lookup.data
|
||||
@@ -357,11 +359,11 @@ export class CatalogDiscoveryService {
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'INVALID_TBANK_TOKEN') {
|
||||
if (error instanceof Error && isTbankErrorCode(error.message, TbankErrorCode.InvalidToken)) {
|
||||
await this.access.rejectMembershipToken(attempt);
|
||||
} else {
|
||||
this.logger.event('warn', 'catalog-discovery.failed', {
|
||||
code: 'TBANK_UNAVAILABLE',
|
||||
code: TbankErrorCode.Unavailable,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -395,7 +397,7 @@ export class CatalogDiscoveryService {
|
||||
this.producers.clear();
|
||||
this.queue.length = 0;
|
||||
this.logger.event('error', 'catalog-discovery.persistence-failed', {
|
||||
code: 'TBANK_UNAVAILABLE',
|
||||
code: TbankErrorCode.Unavailable,
|
||||
});
|
||||
} finally {
|
||||
this.running = false;
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
import { type MoexBondEvent, MoexClient } from '../../../../integrations/moex/moex.api';
|
||||
import { NcrRatingsClient } from '../../../../integrations/ncr/ncr.api';
|
||||
import { decimal } from '../../../../integrations/tbank/decimal';
|
||||
import { isTbankErrorCode, TbankErrorCode } from '../../../../integrations/tbank/tbank-error-code';
|
||||
import {
|
||||
TbankAccessService,
|
||||
type TbankBond,
|
||||
@@ -520,11 +521,11 @@ export class CatalogEnrichmentService {
|
||||
}
|
||||
} catch (error) {
|
||||
failed = true;
|
||||
if (error instanceof Error && error.message === 'INVALID_TBANK_TOKEN') {
|
||||
if (error instanceof Error && isTbankErrorCode(error.message, TbankErrorCode.InvalidToken)) {
|
||||
await this.access.rejectMembershipToken(producer.attempt);
|
||||
} else {
|
||||
this.logger.event('warn', 'catalog-enrichment.failed', {
|
||||
code: 'TBANK_UNAVAILABLE',
|
||||
code: TbankErrorCode.Unavailable,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -572,7 +573,7 @@ export class CatalogEnrichmentService {
|
||||
this.producers.clear();
|
||||
this.queue.length = 0;
|
||||
this.logger.event('error', 'catalog-enrichment.persistence-failed', {
|
||||
code: 'TBANK_UNAVAILABLE',
|
||||
code: TbankErrorCode.Unavailable,
|
||||
});
|
||||
} finally {
|
||||
this.running = false;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { TbankShare } from '../../../../integrations/tbank/tbank.api';
|
||||
|
||||
import { TbankInstrumentType } from '../../../../integrations/tbank/tbank-protocol';
|
||||
|
||||
export const catalogSearchLimit = 30;
|
||||
|
||||
export function selectCatalogInstruments<T extends TbankShare>(
|
||||
@@ -10,8 +12,8 @@ export function selectCatalogInstruments<T extends TbankShare>(
|
||||
const seen = new Set<string>();
|
||||
const kind =
|
||||
instrumentType === 'share'
|
||||
? 'INSTRUMENT_TYPE_SHARE'
|
||||
: 'INSTRUMENT_TYPE_BOND';
|
||||
? TbankInstrumentType.Share
|
||||
: TbankInstrumentType.Bond;
|
||||
const preferredClass = instrumentType === 'share' ? 'TQBR' : 'TQOB';
|
||||
return found
|
||||
.filter(
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
multiply,
|
||||
} from '../../../../../common/decimal';
|
||||
import { ISO_DATE_LENGTH, ISO_DATE_START } from '../../../../../common/time';
|
||||
import { isTbankProtocolValue, TbankCouponType } from '../../../../../integrations/tbank/tbank-protocol';
|
||||
import {
|
||||
TbankAccessService,
|
||||
type TbankBond,
|
||||
@@ -241,10 +242,10 @@ export function bondMetrics(
|
||||
)
|
||||
: null;
|
||||
const couponType =
|
||||
next?.couponType && next.couponType !== 'COUPON_TYPE_UNSPECIFIED'
|
||||
next?.couponType && !isTbankProtocolValue(next.couponType, TbankCouponType.Unspecified)
|
||||
? next.couponType
|
||||
: instrument.floatingCouponFlag === true
|
||||
? 'COUPON_TYPE_FLOATING'
|
||||
? TbankCouponType.Floating
|
||||
: null;
|
||||
return {
|
||||
creditRating,
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum InstrumentErrorCode {
|
||||
BondHistoryStartUnavailable = 'BOND_HISTORY_START_UNAVAILABLE',
|
||||
BondTickerAmbiguous = 'BOND_TICKER_AMBIGUOUS',
|
||||
StockHistoryStartUnavailable = 'STOCK_HISTORY_START_UNAVAILABLE',
|
||||
StockTickerAmbiguous = 'STOCK_TICKER_AMBIGUOUS',
|
||||
}
|
||||
@@ -3,12 +3,9 @@ import { randomUUID } from 'node:crypto';
|
||||
import { isDeepStrictEqual } from 'node:util';
|
||||
|
||||
import type { Prisma } from '../../../../../generated/prisma/client';
|
||||
import type {
|
||||
TbankBond,
|
||||
TbankShare,
|
||||
TbankSyncAttempt,
|
||||
} from '../../../../integrations/tbank/tbank.api';
|
||||
import type { TbankBond, TbankShare, TbankSyncAttempt } from '../../../../integrations/tbank/tbank.api';
|
||||
|
||||
import { TbankErrorCode } from '../../../../integrations/tbank/tbank-error-code';
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
|
||||
const UNCHANGED_REVISION_INCREMENT = 0;
|
||||
@@ -462,7 +459,7 @@ export class CatalogDiscoveryRepository {
|
||||
state: 'running',
|
||||
connection: { invalidToken: true },
|
||||
},
|
||||
data: { state: 'failed', errorCode: 'INVALID_TBANK_TOKEN' },
|
||||
data: { state: 'failed', errorCode: TbankErrorCode.InvalidToken },
|
||||
});
|
||||
await tx.tbankSyncWork.updateMany({
|
||||
where: {
|
||||
@@ -472,7 +469,7 @@ export class CatalogDiscoveryRepository {
|
||||
},
|
||||
data: {
|
||||
state: success ? 'complete' : 'failed',
|
||||
errorCode: success ? null : 'TBANK_UNAVAILABLE',
|
||||
errorCode: success ? null : TbankErrorCode.Unavailable,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,10 +3,9 @@ import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { StructuredLogger } from '../../../../common/logging/structured-logger.service';
|
||||
import { MILLISECONDS_PER_SECOND } from '../../../../common/time';
|
||||
import {
|
||||
TbankAccessService,
|
||||
TbankClient,
|
||||
} from '../../../../integrations/tbank/tbank.api';
|
||||
import { isTbankErrorCode, TbankErrorCode } from '../../../../integrations/tbank/tbank-error-code';
|
||||
import { TbankOperationState } from '../../../../integrations/tbank/tbank-protocol';
|
||||
import { TbankAccessService, TbankClient } from '../../../../integrations/tbank/tbank.api';
|
||||
import { accountHistory } from '../../investment-sync-plan';
|
||||
import {
|
||||
type OperationInterval,
|
||||
@@ -208,7 +207,7 @@ export class BrokerageOperationsSyncService {
|
||||
this.logger.event(
|
||||
'error',
|
||||
'tbank.operations.sync.persistence_failed',
|
||||
{ userId: attempt.userId, code: 'TBANK_UNAVAILABLE' },
|
||||
{ userId: attempt.userId, code: TbankErrorCode.Unavailable },
|
||||
);
|
||||
}
|
||||
const next = pending ? await this.repository.pending(attempt) : null;
|
||||
@@ -272,7 +271,7 @@ export class BrokerageOperationsSyncService {
|
||||
: {}),
|
||||
cursor,
|
||||
limit: operationsPageSize,
|
||||
state: 'OPERATION_STATE_EXECUTED',
|
||||
state: TbankOperationState.Executed,
|
||||
});
|
||||
if (
|
||||
!(await this.repository.stagePage(attempt, interval, cursor, response))
|
||||
@@ -297,7 +296,7 @@ export class BrokerageOperationsSyncService {
|
||||
} catch (error) {
|
||||
await this.repository.fail(
|
||||
attempt,
|
||||
error instanceof Error && error.message === 'INVALID_TBANK_TOKEN',
|
||||
error instanceof Error && isTbankErrorCode(error.message, TbankErrorCode.InvalidToken),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
ledgerRange,
|
||||
planInvestmentScreen,
|
||||
} from '../../investment-sync-plan';
|
||||
import { OperationErrorCode } from '../domain/operation-error-code';
|
||||
import {
|
||||
operationIntervalsOverlap,
|
||||
operationsSyncPriority,
|
||||
@@ -442,7 +443,7 @@ export class BrokerageOperationsService {
|
||||
}
|
||||
return cursor as LocalOperationsCursor;
|
||||
} catch {
|
||||
throw new BadRequestException({ code: 'INVALID_OPERATIONS_CURSOR' });
|
||||
throw new BadRequestException({ code: OperationErrorCode.InvalidOperationsCursor });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum OperationErrorCode {
|
||||
InvalidOperationsCursor = 'INVALID_OPERATIONS_CURSOR',
|
||||
}
|
||||
@@ -1,10 +1,8 @@
|
||||
import { formatDecimal } from '../../../../common/decimal';
|
||||
import { MILLISECONDS_PER_SECOND } from '../../../../common/time';
|
||||
import { decimal } from '../../../../integrations/tbank/decimal';
|
||||
import {
|
||||
TbankMoney,
|
||||
TbankOperationItem,
|
||||
} from '../../../../integrations/tbank/tbank.api';
|
||||
import { TbankOperationType } from '../../../../integrations/tbank/tbank-protocol';
|
||||
import { TbankMoney, TbankOperationItem } from '../../../../integrations/tbank/tbank.api';
|
||||
import {
|
||||
BrokerageOperationDto,
|
||||
OperationCategory,
|
||||
@@ -15,29 +13,29 @@ const MISSING_TIMESTAMP_COMPONENT = 0;
|
||||
const NANOSECONDS_PER_MILLISECOND = 1_000_000;
|
||||
|
||||
const types: Record<string, OperationCategory> = {
|
||||
OPERATION_TYPE_BUY: 'trade',
|
||||
OPERATION_TYPE_BUY_CARD: 'trade',
|
||||
OPERATION_TYPE_SELL: 'trade',
|
||||
OPERATION_TYPE_SELL_CARD: 'trade',
|
||||
OPERATION_TYPE_BUY_MARGIN: 'trade',
|
||||
OPERATION_TYPE_SELL_MARGIN: 'trade',
|
||||
OPERATION_TYPE_COUPON: 'income',
|
||||
OPERATION_TYPE_DIVIDEND: 'income',
|
||||
OPERATION_TYPE_DIVIDEND_TRANSFER: 'income',
|
||||
OPERATION_TYPE_DIV_EXT: 'income',
|
||||
OPERATION_TYPE_TRANS_IIS_BS: 'transfer',
|
||||
OPERATION_TYPE_TRANS_BS_BS: 'transfer',
|
||||
OPERATION_TYPE_BOND_TAX: 'tax',
|
||||
OPERATION_TYPE_TAX: 'tax',
|
||||
OPERATION_TYPE_DIVIDEND_TAX: 'tax',
|
||||
OPERATION_TYPE_SERVICE_FEE: 'fee',
|
||||
OPERATION_TYPE_MARGIN_FEE: 'fee',
|
||||
OPERATION_TYPE_BROKER_FEE: 'fee',
|
||||
OPERATION_TYPE_SUCCESS_FEE: 'fee',
|
||||
OPERATION_TYPE_INPUT: 'transfer',
|
||||
OPERATION_TYPE_OUTPUT: 'transfer',
|
||||
OPERATION_TYPE_INPUT_SECURITIES: 'transfer',
|
||||
OPERATION_TYPE_OUTPUT_SECURITIES: 'transfer',
|
||||
[TbankOperationType.Buy]: 'trade',
|
||||
[TbankOperationType.BuyCard]: 'trade',
|
||||
[TbankOperationType.Sell]: 'trade',
|
||||
[TbankOperationType.SellCard]: 'trade',
|
||||
[TbankOperationType.BuyMargin]: 'trade',
|
||||
[TbankOperationType.SellMargin]: 'trade',
|
||||
[TbankOperationType.Coupon]: 'income',
|
||||
[TbankOperationType.Dividend]: 'income',
|
||||
[TbankOperationType.DividendTransfer]: 'income',
|
||||
[TbankOperationType.DivExt]: 'income',
|
||||
[TbankOperationType.TransIisBs]: 'transfer',
|
||||
[TbankOperationType.TransBsBs]: 'transfer',
|
||||
[TbankOperationType.BondTax]: 'tax',
|
||||
[TbankOperationType.Tax]: 'tax',
|
||||
[TbankOperationType.DividendTax]: 'tax',
|
||||
[TbankOperationType.ServiceFee]: 'fee',
|
||||
[TbankOperationType.MarginFee]: 'fee',
|
||||
[TbankOperationType.BrokerFee]: 'fee',
|
||||
[TbankOperationType.SuccessFee]: 'fee',
|
||||
[TbankOperationType.Input]: 'transfer',
|
||||
[TbankOperationType.Output]: 'transfer',
|
||||
[TbankOperationType.InputSecurities]: 'transfer',
|
||||
[TbankOperationType.OutputSecurities]: 'transfer',
|
||||
};
|
||||
|
||||
export function operationCategory(type: string | undefined): OperationCategory {
|
||||
@@ -51,7 +49,7 @@ export function mapBrokerageOperation(
|
||||
return {
|
||||
id: operation.id ?? operation.cursor ?? '',
|
||||
occurredAt: occurredAt.toISOString(),
|
||||
type: operation.type ?? 'OPERATION_TYPE_UNSPECIFIED',
|
||||
type: operation.type ?? TbankOperationType.Unspecified,
|
||||
category: operationCategory(operation.type),
|
||||
description: operation.description?.trim() || operation.name?.trim() || '',
|
||||
figi: text(operation.figi),
|
||||
|
||||
+6
-3
@@ -12,12 +12,15 @@ import type {
|
||||
TbankOperationsByCursorResponse,
|
||||
} from '../../../../integrations/tbank/tbank.api';
|
||||
|
||||
import { TbankErrorCode } from '../../../../integrations/tbank/tbank-error-code';
|
||||
import { isTbankProtocolValue, TbankOperationState } from '../../../../integrations/tbank/tbank-protocol';
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
import {
|
||||
dateOnlyBounds,
|
||||
DateOnlyRange,
|
||||
moscowDate,
|
||||
} from '../../investment-calendar';
|
||||
import { OperationErrorCode } from '../domain/operation-error-code';
|
||||
import {
|
||||
type OperationInterval,
|
||||
operationIntervals,
|
||||
@@ -137,7 +140,7 @@ export class BrokerageOperationsRepository {
|
||||
)
|
||||
.digest('base64url');
|
||||
if (input.cursor && input.cursor.binding !== binding) {
|
||||
throw new BadRequestException({ code: 'INVALID_OPERATIONS_CURSOR' });
|
||||
throw new BadRequestException({ code: OperationErrorCode.InvalidOperationsCursor });
|
||||
}
|
||||
const scope = {
|
||||
userId: input.userId,
|
||||
@@ -615,7 +618,7 @@ export class BrokerageOperationsRepository {
|
||||
where: { id: attempt.id },
|
||||
data: {
|
||||
state: 'failed',
|
||||
errorCode: invalidToken ? 'INVALID_TBANK_TOKEN' : 'TBANK_UNAVAILABLE',
|
||||
errorCode: invalidToken ? TbankErrorCode.InvalidToken : TbankErrorCode.Unavailable,
|
||||
},
|
||||
});
|
||||
if (invalidToken) {
|
||||
@@ -784,7 +787,7 @@ export class BrokerageOperationsRepository {
|
||||
}
|
||||
const rows = staged.filter((row) => {
|
||||
const state = (row.payload as TbankOperationItem).state;
|
||||
return !state || state === 'OPERATION_STATE_EXECUTED';
|
||||
return !state || isTbankProtocolValue(state, TbankOperationState.Executed);
|
||||
});
|
||||
const scope = {
|
||||
userId: attempt.userId,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { RequestContextService } from '../../../../common/logging/request-contex
|
||||
import { StructuredLogger } from '../../../../common/logging/structured-logger.service';
|
||||
import { MoneyDto } from '../../../../common/money.dto';
|
||||
import { decimal } from '../../../../integrations/tbank/decimal';
|
||||
import { isTbankErrorCode, TbankErrorCode } from '../../../../integrations/tbank/tbank-error-code';
|
||||
import {
|
||||
InstrumentMetadataService,
|
||||
type MetadataIdentity,
|
||||
@@ -387,7 +388,7 @@ export class PortfolioService {
|
||||
} catch (error) {
|
||||
if (
|
||||
error instanceof Error &&
|
||||
error.message === 'INVALID_TBANK_TOKEN'
|
||||
isTbankErrorCode(error.message, TbankErrorCode.InvalidToken)
|
||||
) {
|
||||
await this.access.rejectMembershipToken(attempt);
|
||||
} else await this.repository.fail(attempt);
|
||||
@@ -412,7 +413,7 @@ export class PortfolioService {
|
||||
async get(userId: string, accountId: string): Promise<PortfolioDto> {
|
||||
const result = await this.read(userId, accountId);
|
||||
if (!result.data) {
|
||||
throw new ServiceUnavailableException({ code: 'TBANK_UNAVAILABLE' });
|
||||
throw new ServiceUnavailableException({ code: TbankErrorCode.Unavailable });
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@ import { randomUUID } from 'node:crypto';
|
||||
import type { Prisma } from '../../../../../generated/prisma/client';
|
||||
import type { PortfolioDto } from '../presentation/portfolio.dto';
|
||||
|
||||
import {
|
||||
tbankSourceNeedsRefresh,
|
||||
tbankSourceTtlMs,
|
||||
} from '../../../../integrations/tbank/tbank.api';
|
||||
import { TbankErrorCode } from '../../../../integrations/tbank/tbank-error-code';
|
||||
import { tbankSourceNeedsRefresh, tbankSourceTtlMs } from '../../../../integrations/tbank/tbank.api';
|
||||
import { PrismaService } from '../../../../prisma/prisma.service';
|
||||
|
||||
const EXPECTED_UPDATED_ACCOUNTS = 1;
|
||||
@@ -152,7 +150,7 @@ export class PortfolioRepository {
|
||||
state: 'running',
|
||||
connection: { invalidToken: false },
|
||||
},
|
||||
data: { state: 'failed', errorCode: 'TBANK_UNAVAILABLE' },
|
||||
data: { state: 'failed', errorCode: TbankErrorCode.Unavailable },
|
||||
});
|
||||
return result.count === EXPECTED_UPDATED_ACCOUNTS;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||
|
||||
import { HttpErrorCode } from '../../../common/error-code';
|
||||
import {
|
||||
supportedTbankAccounts,
|
||||
TbankClient,
|
||||
@@ -29,7 +30,7 @@ export class TbankConnectionService {
|
||||
|
||||
async put(userId: string, value: string) {
|
||||
const token = value.trim();
|
||||
if (!token) throw new BadRequestException({ code: 'VALIDATION_ERROR' });
|
||||
if (!token) throw new BadRequestException({ code: HttpErrorCode.ValidationError });
|
||||
const accounts = await this.tokens.translateInvalidToken(() =>
|
||||
this.tbank.validateToken(token),
|
||||
);
|
||||
|
||||
@@ -9,6 +9,7 @@ import { randomUUID } from 'node:crypto';
|
||||
import type { InvestmentResourceMetaDto } from '../investment-resource.dto';
|
||||
|
||||
import { ISO_DATE_LENGTH, ISO_DATE_START } from '../../../common/time';
|
||||
import { TbankErrorCode } from '../../../integrations/tbank/tbank-error-code';
|
||||
import {
|
||||
InstrumentMetadataService,
|
||||
TbankAccessService,
|
||||
@@ -325,7 +326,7 @@ export class TbankSyncService {
|
||||
await this.repository.progress(attempt, children, true);
|
||||
} catch {
|
||||
await this.repository
|
||||
.progress(attempt, children, true, 'TBANK_UNAVAILABLE')
|
||||
.progress(attempt, children, true, TbankErrorCode.Unavailable)
|
||||
.catch(() => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import {
|
||||
UpcomingEventsQueryDto,
|
||||
} from './upcoming-events.dto';
|
||||
|
||||
enum EventsErrorCode { InvalidPeriod = 'INVALID_EVENTS_PERIOD' }
|
||||
|
||||
const FIRST_BATCH_OFFSET = 0;
|
||||
|
||||
const DEFAULT_EVENT_WINDOW_DAYS = 30;
|
||||
@@ -267,7 +269,7 @@ function dateWindow(query: UpcomingEventsQueryDto) {
|
||||
end.setUTCDate(end.getUTCDate() + DEFAULT_EVENT_WINDOW_DAYS);
|
||||
const to = query.to ?? end.toISOString().slice(ISO_DATE_START, ISO_DATE_LENGTH);
|
||||
if (from > to) {
|
||||
throw new BadRequestException({ code: 'INVALID_EVENTS_PERIOD' });
|
||||
throw new BadRequestException({ code: EventsErrorCode.InvalidPeriod });
|
||||
}
|
||||
return { from, to };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user