refactor: organize backend by business responsibility

This commit is contained in:
2026-09-13 13:52:57 +03:00
parent 7fde3ece6e
commit 8ba005346d
145 changed files with 3027 additions and 2346 deletions
@@ -0,0 +1,94 @@
import { Injectable } from '@nestjs/common';
import type { TbankFundamentals } from '../../../../../integrations/tbank/tbank.api';
import { TbankClient } from '../../../../../integrations/tbank/tbank.api';
import { optional } from '../../application/instrument-market';
import { StockCompanyDto, StockMetricDto } from '../presentation/stock.dto';
import { StockCatalogService } from './stock-catalog.service';
@Injectable()
export class GetStockCompanyUseCase {
constructor(
private readonly catalog: StockCatalogService,
private readonly tbank: TbankClient,
) {}
execute(userId: string, uid: string): Promise<StockCompanyDto> {
return this.catalog.cached(userId, `company:${uid}`, async (token) => {
const share = await this.catalog.share(token, uid);
const unavailable: string[] = [];
const assetUid = share.assetUid;
if (!assetUid)
return {
description: null,
metrics: [],
unavailable,
observedAt: new Date().toISOString(),
};
const [description, fundamentals] = await Promise.all([
optional(
() => this.tbank.getAssetDescription(token, assetUid),
'description',
unavailable,
),
optional(
() => this.tbank.getFundamentals(token, assetUid),
'fundamentals',
unavailable,
),
]);
return {
description,
metrics: metrics(fundamentals, share.sector),
unavailable,
observedAt: new Date().toISOString(),
};
});
}
}
function metrics(
values: TbankFundamentals | null,
sector?: string,
): StockMetricDto[] {
if (!values) return [];
const bank = sector?.toLowerCase() === 'financial';
const selection: [
keyof TbankFundamentals,
StockMetricDto['unit'],
StockMetricDto['period'],
][] = [
['marketCapitalization', 'money', null],
['peRatioTtm', 'ratio', 'TTM'],
...(bank
? ([
['priceToBookTtm', 'ratio', 'TTM'],
['roe', 'percent', null],
] as typeof selection)
: ([
['revenueTtm', 'money', 'TTM'],
['ebitdaTtm', 'money', 'TTM'],
] as typeof selection)),
['netIncomeTtm', 'money', 'TTM'],
...(!bank
? ([
['totalDebtMrq', 'money', 'MRQ'],
['netDebtToEbitda', 'ratio', null],
] as typeof selection)
: []),
];
return selection.flatMap(([key, unit, period]) => {
const value = values[key];
const currency = values.currency?.toUpperCase() || null;
return typeof value === 'number' &&
Number.isFinite(value) &&
(unit !== 'money' || currency)
? [
{
key,
value,
unit,
period,
currency: unit === 'money' ? currency : null,
},
]
: [];
});
}