refactor: centralize domain values and enforce numeric lint
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { MILLISECONDS_PER_SECOND } from '../../../../common/time';
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
@@ -15,6 +16,7 @@ import { AccountReferenceDto } from '../../account-reference.dto';
|
||||
import { DateOnlyRange, dateOnlyBounds } from '../../investment-calendar';
|
||||
import {
|
||||
BrokerageOperationDto,
|
||||
DEFAULT_OPERATIONS_PAGE_SIZE,
|
||||
BrokerageOperationsPageDto,
|
||||
BrokerageOperationsQueryDto,
|
||||
} from '../presentation/brokerage-operation.dto';
|
||||
@@ -23,6 +25,8 @@ import {
|
||||
operationCategory,
|
||||
} from '../infrastructure/brokerage-operation.mapper';
|
||||
|
||||
const CONTINUATION_TTL_MS = 300_000;
|
||||
|
||||
type OperationStream = {
|
||||
account: AccountReferenceDto;
|
||||
pending: BrokerageOperationDto[];
|
||||
@@ -54,7 +58,7 @@ export class BrokerageOperationsService {
|
||||
AllOperationsContinuation
|
||||
>({
|
||||
max: 100,
|
||||
ttl: 5 * 60_000,
|
||||
ttl: CONTINUATION_TTL_MS,
|
||||
ttlAutopurge: true,
|
||||
});
|
||||
|
||||
@@ -86,7 +90,10 @@ export class BrokerageOperationsService {
|
||||
query.instrumentUid && !query.from
|
||||
? {
|
||||
...range,
|
||||
from: new Date(Number(account.openedDate?.seconds ?? 0) * 1000),
|
||||
from: new Date(
|
||||
Number(account.openedDate?.seconds ?? 0) *
|
||||
MILLISECONDS_PER_SECOND,
|
||||
),
|
||||
}
|
||||
: range,
|
||||
),
|
||||
@@ -108,7 +115,7 @@ export class BrokerageOperationsService {
|
||||
from: query.from ?? null,
|
||||
to: query.to ?? null,
|
||||
categories: [...new Set(query.categories ?? [])].sort(),
|
||||
limit: query.limit ?? 100,
|
||||
limit: query.limit ?? DEFAULT_OPERATIONS_PAGE_SIZE,
|
||||
});
|
||||
const continuation = query.cursor
|
||||
? this.continuations.get(query.cursor)
|
||||
@@ -178,7 +185,7 @@ export class BrokerageOperationsService {
|
||||
): Promise<BrokerageOperationsPageDto> {
|
||||
const items: BrokerageOperationDto[] = [];
|
||||
// Each stream retains unconsumed rows. A page boundary never discards them.
|
||||
while (items.length < (query.limit ?? 100)) {
|
||||
while (items.length < (query.limit ?? DEFAULT_OPERATIONS_PAGE_SIZE)) {
|
||||
for (const stream of streams) {
|
||||
while (!stream.pending.length && !stream.finished) {
|
||||
const page = await this.page(
|
||||
@@ -324,7 +331,9 @@ export class BrokerageOperationsService {
|
||||
accountId,
|
||||
async ({ token, generation, account }) => {
|
||||
const accountOpenedAt = account.openedDate?.seconds
|
||||
? new Date(Number(account.openedDate.seconds) * 1000)
|
||||
? new Date(
|
||||
Number(account.openedDate.seconds) * MILLISECONDS_PER_SECOND,
|
||||
)
|
||||
: undefined;
|
||||
const selected =
|
||||
typeof ranges === 'function' ? ranges(accountOpenedAt) : ranges;
|
||||
@@ -395,7 +404,7 @@ export class BrokerageOperationsService {
|
||||
from: seconds(range.from),
|
||||
to: seconds(range.to, /^\d{4}-\d{2}-\d{2}$/.test(query.to ?? '')),
|
||||
cursor: query.cursor,
|
||||
limit: query.limit ?? 100,
|
||||
limit: query.limit ?? DEFAULT_OPERATIONS_PAGE_SIZE,
|
||||
state: 'OPERATION_STATE_EXECUTED',
|
||||
...(query.instrumentUid ? { instrumentId: query.instrumentUid } : {}),
|
||||
});
|
||||
@@ -480,7 +489,7 @@ function cacheKey(
|
||||
categories,
|
||||
instrumentUid: query.instrumentUid ?? null,
|
||||
cursor: query.cursor ?? null,
|
||||
limit: query.limit ?? 100,
|
||||
limit: query.limit ?? DEFAULT_OPERATIONS_PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -493,7 +502,7 @@ function dateBoundary(value: string | undefined, end: boolean): Date | null {
|
||||
|
||||
function seconds(value: Date, endOfSecond = false) {
|
||||
return {
|
||||
seconds: Math.floor(value.valueOf() / 1000).toString(),
|
||||
seconds: Math.floor(value.valueOf() / MILLISECONDS_PER_SECOND).toString(),
|
||||
...(endOfSecond ? { nanos: 999_999_999 } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { MILLISECONDS_PER_SECOND } from '../../../../common/time';
|
||||
import { formatDecimal } from '../../../../common/decimal';
|
||||
import {
|
||||
TbankMoney,
|
||||
@@ -84,5 +85,7 @@ function money(value: TbankMoney | undefined) {
|
||||
function timestamp(
|
||||
value: { seconds?: string | number; nanos?: number } | undefined,
|
||||
) {
|
||||
return new Date(Number(value?.seconds ?? 0) * 1000).toISOString();
|
||||
return new Date(
|
||||
Number(value?.seconds ?? 0) * MILLISECONDS_PER_SECOND,
|
||||
).toISOString();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ import { MoneyDto } from '../../../../common/money.dto';
|
||||
|
||||
import { AccountReferenceDto } from '../../account-reference.dto';
|
||||
|
||||
const MAX_CURSOR_LENGTH = 4096;
|
||||
const MAX_OPERATIONS_PAGE_SIZE = 1000;
|
||||
export const DEFAULT_OPERATIONS_PAGE_SIZE = 100;
|
||||
|
||||
export const operationCategories = [
|
||||
'trade',
|
||||
'income',
|
||||
@@ -58,15 +62,19 @@ export class BrokerageOperationsQueryDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(4096)
|
||||
@MaxLength(MAX_CURSOR_LENGTH)
|
||||
cursor?: string;
|
||||
|
||||
@ApiPropertyOptional({ minimum: 1, maximum: 1000, default: 100 })
|
||||
@ApiPropertyOptional({
|
||||
minimum: 1,
|
||||
maximum: MAX_OPERATIONS_PAGE_SIZE,
|
||||
default: DEFAULT_OPERATIONS_PAGE_SIZE,
|
||||
})
|
||||
@IsOptional()
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
@Max(1000)
|
||||
@Max(MAX_OPERATIONS_PAGE_SIZE)
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user