chore: strengthen backend eslint profile

This commit is contained in:
2026-09-28 16:37:01 +03:00
committed by ksv741
parent 53b2794963
commit a970eaaf7a
179 changed files with 4485 additions and 2716 deletions
@@ -1,11 +1,13 @@
import type { Request } from 'express';
import { Controller, Get, Param, Query, Req, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import type { Request } from 'express';
import { ApiProblemResponses } from '../../../common/decorators/api-safe-error-response.decorator';
import { AccessPayload, JwtAuthGuard } from '../../auth/auth.api';
import {
UpcomingEventsResourceDto,
UpcomingEventsQueryDto,
UpcomingEventsResourceDto,
} from './upcoming-events.dto';
import { UpcomingEventsService } from './upcoming-events.service';
@@ -1,11 +1,12 @@
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsDateString, IsOptional, Matches } from 'class-validator';
import { MoneyDto } from '../../../common/money.dto';
import { AccountReferenceDto } from '../account-reference.dto';
import {
InvestmentReadQueryDto,
InvestmentResourceMetaDto,
} from '../investment-resource.dto';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { IsDateString, IsOptional, Matches } from 'class-validator';
import { MoneyDto } from '../../../common/money.dto';
import { AccountReferenceDto } from '../account-reference.dto';
export class UpcomingEventsQueryDto extends InvestmentReadQueryDto {
@ApiPropertyOptional({
@@ -43,10 +44,10 @@ export class UpcomingEventDto {
declare dateKind: 'event' | 'registry';
@ApiProperty({ type: String, nullable: true })
declare figi: string | null;
declare figi: null | string;
@ApiProperty({ type: String, nullable: true })
declare instrumentUid: string | null;
declare instrumentUid: null | string;
@ApiProperty()
declare ticker: string;
@@ -55,10 +56,10 @@ export class UpcomingEventDto {
declare displayName: string;
@ApiProperty({ type: String, nullable: true })
declare isin: string | null;
declare isin: null | string;
@ApiProperty({ type: String, nullable: true })
declare logoName: string | null;
declare logoName: null | string;
@ApiProperty({ type: MoneyDto, nullable: true })
declare projectedAmount: MoneyDto | null;
@@ -80,7 +81,8 @@ export class UpcomingEventsDto {
export class UpcomingEventsResourceDto {
@ApiProperty({ type: UpcomingEventsDto, nullable: true })
declare data: UpcomingEventsDto | null;
declare data: null | UpcomingEventsDto;
@ApiProperty({ type: InvestmentResourceMetaDto })
declare meta: InvestmentResourceMetaDto;
}
@@ -1,8 +1,9 @@
import { InstrumentSourcesModule } from '../instruments/instrument-sources.module';
import { PortfolioModule } from '../portfolio/portfolio.module';
import { Module } from '@nestjs/common';
import { TbankModule } from '../../../integrations/tbank/tbank.module';
import { AuthModule } from '../../auth/auth.module';
import { InstrumentSourcesModule } from '../instruments/instrument-sources.module';
import { PortfolioModule } from '../portfolio/portfolio.module';
import { UpcomingEventsController } from './upcoming-events.controller';
import { UpcomingEventsService } from './upcoming-events.service';
@@ -1,5 +1,5 @@
import { dividendEvent, bondEvent } from './upcoming-events.service';
import { decimalFromString } from '../../../common/decimal';
import { bondEvent, dividendEvent } from './upcoming-events.service';
jest.mock('../../../prisma/prisma.service', () => ({
PrismaService: class {},
}));
@@ -1,5 +1,7 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { MILLISECONDS_PER_SECOND } from '../../../common/time';
import type { MoexBondEvent } from '../../../integrations/moex/moex.api';
import {
add,
decimalFromString,
@@ -8,21 +10,21 @@ import {
multiply,
} from '../../../common/decimal';
import { MoneyDto } from '../../../common/money.dto';
import type { MoexBondEvent } from '../../../integrations/moex/moex.api';
import { MILLISECONDS_PER_SECOND } from '../../../common/time';
import { decimal } from '../../../integrations/tbank/decimal';
import {
TbankAccessService,
InstrumentMetadataService,
metadataKey,
TbankAccessService,
type TbankDividend,
} from '../../../integrations/tbank/tbank.api';
import {
PortfolioService,
ForecastService,
type PortfolioPositionDto,
} from '../portfolio/portfolio.api';
import { CatalogEnrichmentService } from '../instruments/instruments.api';
import { investmentResourceMeta } from '../investment-resource.mapper';
import {
ForecastService,
type PortfolioPositionDto,
PortfolioService,
} from '../portfolio/portfolio.api';
import {
UpcomingEventDto,
UpcomingEventsDto,
@@ -74,7 +76,7 @@ export class UpcomingEventsService {
metadata.get(metadataKey(position))?.data?.ticker ||
'',
}))
.filter((entry): entry is { uid: string; ticker: string } =>
.filter((entry): entry is { ticker: string; uid: string } =>
Boolean(entry.uid),
);
const uids = [...new Set(identities.map((entry) => entry.uid))];
@@ -82,7 +84,7 @@ export class UpcomingEventsService {
let offset = 0;
offset < uids.length;
offset += calendarReadBatchSize
)
) {
sources.push(
...(await this.sources.readMany(
userId,
@@ -98,6 +100,7 @@ export class UpcomingEventsService {
},
)),
);
}
}
for (const position of positions) {
const generic = metadata.get(metadataKey(position))?.data;
@@ -126,7 +129,7 @@ export class UpcomingEventsService {
(source) =>
source.input.uid === uid &&
source.input.kind ===
(type === 'share' ? 'dividends' : 'bond-events'),
(type === 'share' ? 'dividends' : 'bond-events'),
);
const instrument =
sources.find(
@@ -148,15 +151,15 @@ export class UpcomingEventsService {
};
const quantity = decimalFromString(position.quantity);
const projected =
type === 'share'
? (calendar.payload?.dividends ?? [])
type === 'share' ?
(calendar.payload?.dividends ?? [])
.filter(
(event) =>
event.dividendType?.toLowerCase() !== 'cancelled' &&
Boolean(event.recordDate),
)
.map((event) => dividendEvent(identity, quantity, event))
: (calendar.payload?.bondEvents ?? []).map((event) =>
.map((event) => dividendEvent(identity, quantity, event)) :
(calendar.payload?.bondEvents ?? []).map((event) =>
bondEvent(identity, quantity, event),
);
events.push(
@@ -166,9 +169,9 @@ export class UpcomingEventsService {
)
.map((event) => ({
...event,
...(accountId === 'all' && position.account
? { account: position.account }
: {}),
...(accountId === 'all' && position.account ?
{ account: position.account } :
{}),
})),
);
}
@@ -179,14 +182,14 @@ export class UpcomingEventsService {
a.type.localeCompare(b.type) ||
a.ticker.localeCompare(b.ticker),
);
const candidate: UpcomingEventsDto | undefined = ready
? {
const candidate: undefined | UpcomingEventsDto = ready ?
{
accountId,
calculatedAt: new Date().toISOString(),
events,
incomeTotalsByCurrency: incomeTotals(events),
}
: undefined;
} :
undefined;
return this.forecasts.result({
userId,
revision: connection.revision,
@@ -197,6 +200,7 @@ export class UpcomingEventsService {
candidate,
});
}
getAll(userId: string, query: UpcomingEventsQueryDto = {}) {
return this.get(userId, 'all', query);
}
@@ -205,7 +209,7 @@ export class UpcomingEventsService {
export function dividendEvent(
metadata: Pick<
UpcomingEventDto,
'figi' | 'instrumentUid' | 'ticker' | 'displayName' | 'isin' | 'logoName'
'displayName' | 'figi' | 'instrumentUid' | 'isin' | 'logoName' | 'ticker'
>,
quantity: ReturnType<typeof decimal>,
event: TbankDividend,
@@ -217,20 +221,20 @@ export function dividendEvent(
.toISOString()
.slice(0, 10),
dateKind: 'registry',
projectedAmount: event.dividendNet
? projectedMoney(
projectedAmount: event.dividendNet ?
projectedMoney(
quantity,
formatDecimal(decimal(event.dividendNet)),
event.dividendNet.currency,
)
: null,
) :
null,
};
}
export function bondEvent(
metadata: Pick<
UpcomingEventDto,
'figi' | 'instrumentUid' | 'ticker' | 'displayName' | 'isin' | 'logoName'
'displayName' | 'figi' | 'instrumentUid' | 'isin' | 'logoName' | 'ticker'
>,
quantity: ReturnType<typeof decimal>,
event: MoexBondEvent,
@@ -241,16 +245,16 @@ export function bondEvent(
date: event.date,
dateKind: 'event',
projectedAmount:
'value' in event
? projectedMoney(quantity, event.value, event.currency)
: null,
'value' in event ?
projectedMoney(quantity, event.value, event.currency) :
null,
};
}
function supported(position: Position): boolean {
return (
!isZero(decimalFromString(position.quantity)) &&
['STOCK', 'BOND'].includes(position.type)
['BOND', 'STOCK'].includes(position.type)
);
}
@@ -259,8 +263,9 @@ function dateWindow(query: UpcomingEventsQueryDto) {
const end = new Date(`${from}T00:00:00.000Z`);
end.setUTCDate(end.getUTCDate() + DEFAULT_EVENT_WINDOW_DAYS);
const to = query.to ?? end.toISOString().slice(0, 10);
if (from > to)
if (from > to) {
throw new BadRequestException({ code: 'INVALID_EVENTS_PERIOD' });
}
return { from, to };
}
@@ -280,8 +285,9 @@ function projectedMoney(
function incomeTotals(events: UpcomingEventDto[]): MoneyDto[] {
const totals = new Map<string, ReturnType<typeof decimal>>();
for (const event of events) {
if (!['coupon', 'dividend'].includes(event.type) || !event.projectedAmount)
if (!['coupon', 'dividend'].includes(event.type) || !event.projectedAmount) {
continue;
}
const current = totals.get(event.projectedAmount.currency) ?? {
value: 0n,
scale: 0,