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,10 +1,10 @@
import { CatalogEnrichmentService } from './catalog-enrichment.service';
import {
ConflictException,
Injectable,
NotFoundException,
ConflictException,
} from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import { StructuredLogger } from '../../../../common/logging/structured-logger.service';
import {
TbankAccessService,
@@ -17,9 +17,10 @@ import { investmentResourceMeta } from '../../investment-resource.mapper';
import {
CatalogDiscoveryRepository,
catalogQuery,
catalogScope,
type CatalogQuery,
catalogScope,
} from '../infrastructure/catalog-discovery.repository';
import { CatalogEnrichmentService } from './catalog-enrichment.service';
import {
catalogSearchLimit,
selectCatalogInstruments,
@@ -28,10 +29,10 @@ import {
const tickerAmbiguityLimit = 2;
type Producer = {
input: CatalogQuery;
attempt: TbankSyncAttempt;
observers: TbankSyncAttempt[];
force: boolean;
input: CatalogQuery;
observers: TbankSyncAttempt[];
};
@Injectable()
export class CatalogDiscoveryService {
@@ -43,8 +44,9 @@ export class CatalogDiscoveryService {
private running = false;
private readonly completions = new Map<
string,
{ workId: string; done: Promise<void>; resolve: () => void }
{ done: Promise<void>; resolve: () => void; workId: string }
>();
constructor(
private readonly repository: CatalogDiscoveryRepository,
private readonly access: TbankAccessService,
@@ -73,14 +75,14 @@ export class CatalogDiscoveryService {
async search(userId: string, input: CatalogQuery, observe = false) {
const discovery = await this.read(userId, input, observe);
const sources = discovery.data
? await this.enrichment.readMany(
const sources = discovery.data ?
await this.enrichment.readMany(
userId,
input.instrumentType,
discovery.data.map((item) => item.uid),
observe,
)
: [];
) :
[];
const dependencies = [
discovery.meta,
...sources.map((source) => source.meta),
@@ -108,22 +110,22 @@ export class CatalogDiscoveryService {
.map((dependency) => dependency.dataRevision)
.join('/'),
availability:
discovery.data === null
? 'missing'
: sources.every((source) => source.meta.availability === 'complete')
? 'complete'
: 'partial',
fetchedAt: discovery.meta.lastSuccessfulAt
? new Date(discovery.meta.lastSuccessfulAt)
: null,
discovery.data === null ?
'missing' :
sources.every((source) => source.meta.availability === 'complete') ?
'complete' :
'partial',
fetchedAt: discovery.meta.lastSuccessfulAt ?
new Date(discovery.meta.lastSuccessfulAt) :
null,
freshForMs: tbankSourceTtlMs.catalog,
work: discovery.meta.sync.id
? {
work: discovery.meta.sync.id ?
{
id: discovery.meta.sync.id,
state: discovery.meta.sync.state,
errorCode: discovery.meta.sync.errorCode,
}
: null,
} :
null,
dependencies,
}),
};
@@ -131,7 +133,7 @@ export class CatalogDiscoveryService {
async detail(
userId: string,
instrumentType: 'share' | 'bond',
instrumentType: 'bond' | 'share',
uid: string,
observe = false,
) {
@@ -143,22 +145,24 @@ export class CatalogDiscoveryService {
['detail'],
);
const source = sources[0];
if (source.meta.availability === 'complete' && !source.instrument)
if (source.meta.availability === 'complete' && !source.instrument) {
throw new NotFoundException();
}
const instrument = source.instrument;
let slug = uid;
const dependencies = [source.meta];
if (
instrument?.ticker &&
/^[A-Za-z0-9][A-Za-z0-9._@+-]{0,79}$/.test(instrument.ticker)
/^[A-Z0-9][\w.@+-]{0,79}$/i.test(instrument.ticker)
) {
const alias = await this.read(
userId,
{ instrumentType, query: instrument.ticker, exactTicker: true },
observe,
);
if (alias.data?.length === 1 && alias.data[0].uid === uid)
if (alias.data?.length === 1 && alias.data[0].uid === uid) {
slug = instrument.ticker.toUpperCase();
}
dependencies.push(alias.meta);
}
return {
@@ -169,17 +173,17 @@ export class CatalogDiscoveryService {
.map((dependency) => dependency.dataRevision)
.join('/'),
availability: source.meta.availability,
fetchedAt: source.meta.lastSuccessfulAt
? new Date(source.meta.lastSuccessfulAt)
: null,
fetchedAt: source.meta.lastSuccessfulAt ?
new Date(source.meta.lastSuccessfulAt) :
null,
freshForMs: tbankSourceTtlMs.metadata,
work: source.meta.sync.id
? {
work: source.meta.sync.id ?
{
id: source.meta.sync.id,
state: source.meta.sync.state,
errorCode: source.meta.sync.errorCode,
}
: null,
} :
null,
dependencies,
}),
};
@@ -187,7 +191,7 @@ export class CatalogDiscoveryService {
async byTicker(
userId: string,
instrumentType: 'share' | 'bond',
instrumentType: 'bond' | 'share',
ticker: string,
observe = false,
) {
@@ -197,38 +201,39 @@ export class CatalogDiscoveryService {
observe,
);
if (lookup.data?.length === 0) throw new NotFoundException();
if (lookup.data && lookup.data.length > 1)
if (lookup.data && lookup.data.length > 1) {
throw new ConflictException({
code:
instrumentType === 'share'
? 'STOCK_TICKER_AMBIGUOUS'
: 'BOND_TICKER_AMBIGUOUS',
instrumentType === 'share' ?
'STOCK_TICKER_AMBIGUOUS' :
'BOND_TICKER_AMBIGUOUS',
});
const result = lookup.data
? await this.detail(userId, instrumentType, lookup.data[0].uid, observe)
: null;
}
const result = lookup.data ?
await this.detail(userId, instrumentType, lookup.data[0].uid, observe) :
null;
const dependencies = [lookup.meta, ...(result ? [result.meta] : [])];
return {
data: result?.data
? { ...result.data, slug: ticker.trim().toUpperCase() }
: null,
data: result?.data ?
{ ...result.data, slug: ticker.trim().toUpperCase() } :
null,
meta: investmentResourceMeta({
resourceKey: `${instrumentType}/ticker/${ticker.trim().toUpperCase()}`,
dataRevision: dependencies
.map((dependency) => dependency.dataRevision)
.join('/'),
availability: result?.meta.availability ?? 'missing',
fetchedAt: lookup.meta.lastSuccessfulAt
? new Date(lookup.meta.lastSuccessfulAt)
: null,
fetchedAt: lookup.meta.lastSuccessfulAt ?
new Date(lookup.meta.lastSuccessfulAt) :
null,
freshForMs: tbankSourceTtlMs.metadata,
work: lookup.meta.sync.id
? {
work: lookup.meta.sync.id ?
{
id: lookup.meta.sync.id,
state: lookup.meta.sync.state,
errorCode: lookup.meta.sync.errorCode,
}
: null,
} :
null,
dependencies,
}),
};
@@ -245,8 +250,9 @@ export class CatalogDiscoveryService {
const saved = await this.repository.read(input);
if (
!tbankSourceNeedsRefresh('catalog', saved.fetchedAt, new Date(), force)
)
) {
return null;
}
const reservation = await this.repository.reserve(
{ userId, revision },
input,
@@ -291,12 +297,13 @@ export class CatalogDiscoveryService {
async refresh(userId: string, revision: string, query: CatalogQuery) {
const id = await this.request(userId, revision, query, true);
if (id)
if (id) {
await Promise.all(
[...this.completions.values()]
.filter((entry) => entry.workId === id)
.map((entry) => entry.done),
);
}
const saved = await this.repository.read(catalogQuery(query));
const sources = await this.enrichment.readMany(
userId,
@@ -323,19 +330,19 @@ export class CatalogDiscoveryService {
const token = await this.access.tokenForAttempt(attempt);
if (token) {
const found =
input.instrumentType === 'share'
? await this.tbank.findShares(token, input.query)
: await this.tbank.findBonds(token, input.query);
input.instrumentType === 'share' ?
await this.tbank.findShares(token, input.query) :
await this.tbank.findBonds(token, input.query);
success = await this.repository.publish(
attempt,
input,
selectCatalogInstruments(
input.exactTicker
? found.filter(
input.exactTicker ?
found.filter(
(item) =>
item.ticker?.trim().toUpperCase() === input.query,
)
: found,
) :
found,
input.query,
input.instrumentType,
).slice(
@@ -346,17 +353,18 @@ export class CatalogDiscoveryService {
);
}
} catch (error) {
if (error instanceof Error && error.message === 'INVALID_TBANK_TOKEN')
if (error instanceof Error && error.message === 'INVALID_TBANK_TOKEN') {
await this.access.rejectMembershipToken(attempt);
else
} else {
this.logger.event('warn', 'catalog-discovery.failed', {
code: 'TBANK_UNAVAILABLE',
});
}
}
if (success) {
const saved = await this.repository.read(input);
for (const observer of [attempt, ...producer.observers])
if (!input.exactTicker || saved.data?.length === 1)
for (const observer of [attempt, ...producer.observers]) {
if (!input.exactTicker || saved.data?.length === 1) {
await this.enrichment.requestMany(
observer.userId,
observer.revision,
@@ -365,6 +373,8 @@ export class CatalogDiscoveryService {
producer.force,
input.exactTicker ? ['detail'] : ['last-price', 'detail'],
);
}
}
}
this.producers.delete(catalogScope(input));
for (const observer of [attempt, ...producer.observers]) {