From b79c8210dc1ef2ee95d7f42fef220786ad495b6d Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 13 Jun 2026 20:32:34 +0300 Subject: [PATCH] style: apply prettier formatting across the codebase --- .../common/filters/http-exception.filter.ts | 8 +- .../interceptors/transform.interceptor.ts | 16 +- apps/backend/src/config/configuration.ts | 5 +- apps/backend/src/main.ts | 5 +- .../src/modules/bonds/bonds.service.spec.ts | 6 +- .../src/modules/bonds/bonds.service.ts | 12 +- .../modules/candles/candles.service.spec.ts | 6 +- .../src/modules/candles/candles.service.ts | 3 +- .../moex-client/moex-client.service.spec.ts | 4 +- .../moex-client/moex-client.service.ts | 35 +- .../securities/securities.controller.spec.ts | 14 +- .../securities/securities.service.spec.ts | 6 +- .../modules/securities/securities.service.ts | 11 +- .../src/modules/shares/shares.service.spec.ts | 6 +- .../src/modules/shares/shares.service.ts | 15 +- apps/frontend/src/api/client.ts | 45 +- apps/frontend/src/api/types.ts | 780 +++++++++--------- apps/frontend/src/components/BondDetails.tsx | 25 +- apps/frontend/src/components/Layout.tsx | 10 +- apps/frontend/src/components/SearchBar.tsx | 19 +- apps/frontend/src/components/StockDetails.tsx | 23 +- apps/frontend/src/pages/BondPage.tsx | 9 +- apps/frontend/src/pages/HomePage.tsx | 8 +- apps/frontend/src/pages/StockPage.tsx | 18 +- 24 files changed, 584 insertions(+), 505 deletions(-) diff --git a/apps/backend/src/common/filters/http-exception.filter.ts b/apps/backend/src/common/filters/http-exception.filter.ts index 373205f..04eec41 100644 --- a/apps/backend/src/common/filters/http-exception.filter.ts +++ b/apps/backend/src/common/filters/http-exception.filter.ts @@ -1,10 +1,4 @@ -import { - ExceptionFilter, - Catch, - ArgumentsHost, - HttpException, - HttpStatus, -} from '@nestjs/common'; +import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; import { Response } from 'express'; @Catch() diff --git a/apps/backend/src/common/interceptors/transform.interceptor.ts b/apps/backend/src/common/interceptors/transform.interceptor.ts index b91ca26..cd6f888 100644 --- a/apps/backend/src/common/interceptors/transform.interceptor.ts +++ b/apps/backend/src/common/interceptors/transform.interceptor.ts @@ -1,21 +1,11 @@ -import { - Injectable, - NestInterceptor, - ExecutionContext, - CallHandler, -} from '@nestjs/common'; +import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { ApiResponse } from '../dto/api-response.dto'; @Injectable() -export class TransformInterceptor - implements NestInterceptor> -{ - intercept( - context: ExecutionContext, - next: CallHandler, - ): Observable> { +export class TransformInterceptor implements NestInterceptor> { + intercept(context: ExecutionContext, next: CallHandler): Observable> { return next.handle().pipe( map((data) => { if (data instanceof ApiResponse) return data; diff --git a/apps/backend/src/config/configuration.ts b/apps/backend/src/config/configuration.ts index 22872dc..cd9bc47 100644 --- a/apps/backend/src/config/configuration.ts +++ b/apps/backend/src/config/configuration.ts @@ -5,10 +5,7 @@ export default registerAs('app', () => ({ moex: { baseUrl: process.env.MOEX_BASE_URL || 'https://iss.moex.com/iss', rateLimit: parseInt(process.env.MOEX_RATE_LIMIT || '10', 10), - circuitBreakerThreshold: parseInt( - process.env.MOEX_CIRCUIT_BREAKER_THRESHOLD || '5', - 10, - ), + circuitBreakerThreshold: parseInt(process.env.MOEX_CIRCUIT_BREAKER_THRESHOLD || '5', 10), circuitBreakerResetSeconds: parseInt( process.env.MOEX_CIRCUIT_BREAKER_RESET_SECONDS || '30', 10, diff --git a/apps/backend/src/main.ts b/apps/backend/src/main.ts index 6b04342..9e57aa2 100644 --- a/apps/backend/src/main.ts +++ b/apps/backend/src/main.ts @@ -20,10 +20,7 @@ async function bootstrap() { app.enableCors(); - const config = new DocumentBuilder() - .setTitle('MoexVibe API') - .setVersion('1.0.0') - .build(); + const config = new DocumentBuilder().setTitle('MoexVibe API').setVersion('1.0.0').build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, document); diff --git a/apps/backend/src/modules/bonds/bonds.service.spec.ts b/apps/backend/src/modules/bonds/bonds.service.spec.ts index e380db7..903de5c 100644 --- a/apps/backend/src/modules/bonds/bonds.service.spec.ts +++ b/apps/backend/src/modules/bonds/bonds.service.spec.ts @@ -16,7 +16,11 @@ describe('BondsService', () => { MoexClientService, { provide: 'CACHE_MANAGER', - useValue: { get: () => undefined, set: () => Promise.resolve(), del: () => Promise.resolve() }, + useValue: { + get: () => undefined, + set: () => Promise.resolve(), + del: () => Promise.resolve(), + }, }, CacheService, ], diff --git a/apps/backend/src/modules/bonds/bonds.service.ts b/apps/backend/src/modules/bonds/bonds.service.ts index c8e0e44..6ccd3f7 100644 --- a/apps/backend/src/modules/bonds/bonds.service.ts +++ b/apps/backend/src/modules/bonds/bonds.service.ts @@ -10,7 +10,11 @@ export class BondsService { ) {} async getBond(secid: string) { - const { data: bond, fromCache, cachedAt } = await this.cache.getOrFetch( + const { + data: bond, + fromCache, + cachedAt, + } = await this.cache.getOrFetch( 'bond', [secid], () => this.moexClient.getBondData(secid), @@ -71,7 +75,11 @@ export class BondsService { } async getMarketData(secid: string) { - const { data: mkt, fromCache, cachedAt } = await this.cache.getOrFetch( + const { + data: mkt, + fromCache, + cachedAt, + } = await this.cache.getOrFetch( 'marketdata', ['bonds', secid], () => this.moexClient.getBondMarketData(secid), diff --git a/apps/backend/src/modules/candles/candles.service.spec.ts b/apps/backend/src/modules/candles/candles.service.spec.ts index 35833e4..fd37b24 100644 --- a/apps/backend/src/modules/candles/candles.service.spec.ts +++ b/apps/backend/src/modules/candles/candles.service.spec.ts @@ -17,7 +17,11 @@ describe('CandlesService', () => { MoexClientService, { provide: 'CACHE_MANAGER', - useValue: { get: () => undefined, set: () => Promise.resolve(), del: () => Promise.resolve() }, + useValue: { + get: () => undefined, + set: () => Promise.resolve(), + del: () => Promise.resolve(), + }, }, CacheService, ], diff --git a/apps/backend/src/modules/candles/candles.service.ts b/apps/backend/src/modules/candles/candles.service.ts index 8754624..0cb4ae4 100644 --- a/apps/backend/src/modules/candles/candles.service.ts +++ b/apps/backend/src/modules/candles/candles.service.ts @@ -25,8 +25,7 @@ export class CandlesService { const { data, fromCache, cachedAt } = await this.cache.getOrFetch( 'candles', [market, secid, String(moexInterval), from, till], - () => - this.moexClient.getCandles('stock', market, secid, moexInterval, from, till), + () => this.moexClient.getCandles('stock', market, secid, moexInterval, from, till), 'candlesTtl', ); diff --git a/apps/backend/src/modules/moex-client/moex-client.service.spec.ts b/apps/backend/src/modules/moex-client/moex-client.service.spec.ts index cb0cc50..5921972 100644 --- a/apps/backend/src/modules/moex-client/moex-client.service.spec.ts +++ b/apps/backend/src/modules/moex-client/moex-client.service.spec.ts @@ -9,9 +9,7 @@ describe('MoexClientService', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ - imports: [ - ConfigModule.forRoot({ load: [configuration] }), - ], + imports: [ConfigModule.forRoot({ load: [configuration] })], providers: [MoexClientService], }).compile(); diff --git a/apps/backend/src/modules/moex-client/moex-client.service.ts b/apps/backend/src/modules/moex-client/moex-client.service.ts index f54e056..ce83a64 100644 --- a/apps/backend/src/modules/moex-client/moex-client.service.ts +++ b/apps/backend/src/modules/moex-client/moex-client.service.ts @@ -25,19 +25,9 @@ export class MoexClientService { constructor(private configService: ConfigService) { const baseUrl = this.configService.get('app.moex.baseUrl')!; - this.threshold = this.configService.get( - 'app.moex.circuitBreakerThreshold', - 5, - ); - this.resetMs = - this.configService.get( - 'app.moex.circuitBreakerResetSeconds', - 30, - ) * 1000; - const rateLimit = this.configService.get( - 'app.moex.rateLimit', - 10, - ); + this.threshold = this.configService.get('app.moex.circuitBreakerThreshold', 5); + this.resetMs = this.configService.get('app.moex.circuitBreakerResetSeconds', 30) * 1000; + const rateLimit = this.configService.get('app.moex.rateLimit', 10); this.client = axios.create({ baseURL: baseUrl, @@ -163,7 +153,9 @@ export class MoexClientService { open: mkt ? parseFloat((mkt.OPEN as string) || '') : null, low: mkt ? parseFloat((mkt.LOW as string) || '') : null, high: mkt ? parseFloat((mkt.HIGH as string) || '') : null, - last: mkt ? parseFloat((mkt.LAST as string) || '') : parseFloat((share.PREVPRICE as string) || ''), + last: mkt + ? parseFloat((mkt.LAST as string) || '') + : parseFloat((share.PREVPRICE as string) || ''), lastChange: mkt ? parseFloat((mkt.LASTCHANGE as string) || '') : null, lastChangePrcnt: mkt ? parseFloat((mkt.LASTCHANGEPRCNT as string) || '') : null, volume: mkt ? parseInt((mkt.VOLTODAY as string) || '0', 10) : 0, @@ -278,11 +270,7 @@ export class MoexClientService { })); } - async getHistory( - secid: string, - from: string, - till: string, - ): Promise { + async getHistory(secid: string, from: string, till: string): Promise { const data = await this.request>( `/engines/stock/markets/shares/securities/${secid}`, { from, till }, @@ -304,11 +292,7 @@ export class MoexClientService { })); } - async getBondHistory( - secid: string, - from: string, - till: string, - ): Promise { + async getBondHistory(secid: string, from: string, till: string): Promise { const data = await this.request>( `/engines/stock/markets/bonds/securities/${secid}`, { from, till }, @@ -320,8 +304,7 @@ export class MoexClientService { return this.extractTable(data, tableName).map((h) => ({ tradeDate: h.TRADEDATE as string, close: h.CLOSE != null ? parseFloat(h.CLOSE as string) : null, - legalClosePrice: - h.LEGALCLOSEPRICE != null ? parseFloat(h.LEGALCLOSEPRICE as string) : null, + legalClosePrice: h.LEGALCLOSEPRICE != null ? parseFloat(h.LEGALCLOSEPRICE as string) : null, waprice: h.WAPRICE != null ? parseFloat(h.WAPRICE as string) : null, yieldClose: h.YIELDCLOSE != null ? parseFloat(h.YIELDCLOSE as string) : null, duration: h.DURATION != null ? parseFloat(h.DURATION as string) : null, diff --git a/apps/backend/src/modules/securities/securities.controller.spec.ts b/apps/backend/src/modules/securities/securities.controller.spec.ts index 7b30671..c7f2b1f 100644 --- a/apps/backend/src/modules/securities/securities.controller.spec.ts +++ b/apps/backend/src/modules/securities/securities.controller.spec.ts @@ -8,7 +8,15 @@ describe('SecuritiesController', () => { let service: SecuritiesService; const mockResults = [ - { secid: 'SBER', isin: 'RU0009029540', shortName: 'Сбербанк', type: 'share', listLevel: 1, currency: 'RUB', price: null }, + { + secid: 'SBER', + isin: 'RU0009029540', + shortName: 'Сбербанк', + type: 'share', + listLevel: 1, + currency: 'RUB', + price: null, + }, ]; const mockService = { @@ -18,9 +26,7 @@ describe('SecuritiesController', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ controllers: [SecuritiesController], - providers: [ - { provide: SecuritiesService, useValue: mockService }, - ], + providers: [{ provide: SecuritiesService, useValue: mockService }], }).compile(); controller = module.get(SecuritiesController); diff --git a/apps/backend/src/modules/securities/securities.service.spec.ts b/apps/backend/src/modules/securities/securities.service.spec.ts index bc68f1c..d0370e3 100644 --- a/apps/backend/src/modules/securities/securities.service.spec.ts +++ b/apps/backend/src/modules/securities/securities.service.spec.ts @@ -17,7 +17,11 @@ describe('SecuritiesService', () => { MoexClientService, { provide: 'CACHE_MANAGER', - useValue: { get: () => undefined, set: () => Promise.resolve(), del: () => Promise.resolve() }, + useValue: { + get: () => undefined, + set: () => Promise.resolve(), + del: () => Promise.resolve(), + }, }, CacheService, ], diff --git a/apps/backend/src/modules/securities/securities.service.ts b/apps/backend/src/modules/securities/securities.service.ts index 4a42ae6..f84315e 100644 --- a/apps/backend/src/modules/securities/securities.service.ts +++ b/apps/backend/src/modules/securities/securities.service.ts @@ -28,9 +28,14 @@ export class SecuritiesService { const results = await this.moexClient.searchSecurities(query); return results .map((s): SearchResultItem | null => { - const type = (s.group === 'stock_shares' || s.type === 'common_share' || s.type === 'preferred_share') - ? 'share' as const - : (s.group === 'stock_bonds' ? 'bond' as const : null); + const type = + s.group === 'stock_shares' || + s.type === 'common_share' || + s.type === 'preferred_share' + ? ('share' as const) + : s.group === 'stock_bonds' + ? ('bond' as const) + : null; if (!type) return null; return { secid: s.secid, diff --git a/apps/backend/src/modules/shares/shares.service.spec.ts b/apps/backend/src/modules/shares/shares.service.spec.ts index 33e499d..82ab025 100644 --- a/apps/backend/src/modules/shares/shares.service.spec.ts +++ b/apps/backend/src/modules/shares/shares.service.spec.ts @@ -16,7 +16,11 @@ describe('SharesService', () => { MoexClientService, { provide: 'CACHE_MANAGER', - useValue: { get: () => undefined, set: () => Promise.resolve(), del: () => Promise.resolve() }, + useValue: { + get: () => undefined, + set: () => Promise.resolve(), + del: () => Promise.resolve(), + }, }, CacheService, ], diff --git a/apps/backend/src/modules/shares/shares.service.ts b/apps/backend/src/modules/shares/shares.service.ts index 0afc476..85e260b 100644 --- a/apps/backend/src/modules/shares/shares.service.ts +++ b/apps/backend/src/modules/shares/shares.service.ts @@ -11,7 +11,14 @@ export class SharesService { async getShare(secid: string) { const desc = await this.moexClient.getSecurityDescription(secid); - if (!desc || !(desc.group === 'stock_shares' || desc.type === 'common_share' || desc.type === 'preferred_share')) { + if ( + !desc || + !( + desc.group === 'stock_shares' || + desc.type === 'common_share' || + desc.type === 'preferred_share' + ) + ) { throw new NotFoundException(`Share ${secid} not found`); } @@ -55,7 +62,11 @@ export class SharesService { } async getMarketData(secid: string) { - const { data: marketData, fromCache, cachedAt } = await this.cache.getOrFetch( + const { + data: marketData, + fromCache, + cachedAt, + } = await this.cache.getOrFetch( 'marketdata', ['shares', secid], () => this.moexClient.getShareMarketData(secid), diff --git a/apps/frontend/src/api/client.ts b/apps/frontend/src/api/client.ts index 42c4a18..46db16f 100644 --- a/apps/frontend/src/api/client.ts +++ b/apps/frontend/src/api/client.ts @@ -15,7 +15,10 @@ import type { const BASE = ''; -async function request(path: string, params?: Record): Promise<{ data: T; meta: ApiResponseMeta }> { +async function request( + path: string, + params?: Record, +): Promise<{ data: T; meta: ApiResponseMeta }> { const url = new URL(`${BASE}${path}`, window.location.origin); if (params) { for (const [k, v] of Object.entries(params)) { @@ -37,19 +40,31 @@ export function searchSecurities( type: 'all' | 'share' | 'bond' = 'all', limit = 20, ): Promise<{ data: SearchResultItem[]; meta: ApiResponseMeta }> { - return request('/api/v1/securities/search', { q, type, limit: String(limit) }); + return request('/api/v1/securities/search', { + q, + type, + limit: String(limit), + }); } export function getShare(secid: string): Promise<{ data: ShareResponse; meta: ApiResponseMeta }> { return request(`/api/v1/securities/shares/${encodeURIComponent(secid)}`); } -export function getShareMarketData(secid: string): Promise<{ data: StockMarketData; meta: ApiResponseMeta }> { - return request(`/api/v1/securities/shares/${encodeURIComponent(secid)}/marketdata`); +export function getShareMarketData( + secid: string, +): Promise<{ data: StockMarketData; meta: ApiResponseMeta }> { + return request( + `/api/v1/securities/shares/${encodeURIComponent(secid)}/marketdata`, + ); } -export function getShareDividends(secid: string): Promise<{ data: DividendItem[]; meta: ApiResponseMeta }> { - return request(`/api/v1/securities/shares/${encodeURIComponent(secid)}/dividends`); +export function getShareDividends( + secid: string, +): Promise<{ data: DividendItem[]; meta: ApiResponseMeta }> { + return request( + `/api/v1/securities/shares/${encodeURIComponent(secid)}/dividends`, + ); } export function getShareHistory( @@ -57,15 +72,22 @@ export function getShareHistory( from: string, till: string, ): Promise<{ data: ShareHistoryItem[]; meta: ApiResponseMeta }> { - return request(`/api/v1/securities/shares/${encodeURIComponent(secid)}/history`, { from, till }); + return request( + `/api/v1/securities/shares/${encodeURIComponent(secid)}/history`, + { from, till }, + ); } export function getBond(secid: string): Promise<{ data: BondResponse; meta: ApiResponseMeta }> { return request(`/api/v1/securities/bonds/${encodeURIComponent(secid)}`); } -export function getBondMarketData(secid: string): Promise<{ data: BondMarketData; meta: ApiResponseMeta }> { - return request(`/api/v1/securities/bonds/${encodeURIComponent(secid)}/marketdata`); +export function getBondMarketData( + secid: string, +): Promise<{ data: BondMarketData; meta: ApiResponseMeta }> { + return request( + `/api/v1/securities/bonds/${encodeURIComponent(secid)}/marketdata`, + ); } export function getBondHistory( @@ -73,7 +95,10 @@ export function getBondHistory( from: string, till: string, ): Promise<{ data: BondHistoryItem[]; meta: ApiResponseMeta }> { - return request(`/api/v1/securities/bonds/${encodeURIComponent(secid)}/history`, { from, till }); + return request( + `/api/v1/securities/bonds/${encodeURIComponent(secid)}/history`, + { from, till }, + ); } export function getShareCandles( diff --git a/apps/frontend/src/api/types.ts b/apps/frontend/src/api/types.ts index ebaadb4..0a51dc8 100644 --- a/apps/frontend/src/api/types.ts +++ b/apps/frontend/src/api/types.ts @@ -4,427 +4,427 @@ */ export interface paths { - "/api/v1/health": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Проверка состояния сервиса */ - get: operations["HealthController_check"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + '/api/v1/health': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/search": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Поиск по инструментам */ - get: operations["SecuritiesController_search"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Проверка состояния сервиса */ + get: operations['HealthController_check']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/search': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/shares/{secid}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить спецификацию акции */ - get: operations["SharesController_getShare"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Поиск по инструментам */ + get: operations['SecuritiesController_search']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/shares/{secid}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/shares/{secid}/marketdata": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить рыночные данные акции */ - get: operations["SharesController_getMarketData"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Получить спецификацию акции */ + get: operations['SharesController_getShare']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/shares/{secid}/marketdata': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/shares/{secid}/dividends": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить дивиденды */ - get: operations["SharesController_getDividends"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Получить рыночные данные акции */ + get: operations['SharesController_getMarketData']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/shares/{secid}/dividends': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/shares/{secid}/history": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить дневную историю торгов акции */ - get: operations["SharesController_getHistory"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Получить дивиденды */ + get: operations['SharesController_getDividends']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/shares/{secid}/history': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/bonds/{secid}": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить спецификацию облигации */ - get: operations["BondsController_getBond"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Получить дневную историю торгов акции */ + get: operations['SharesController_getHistory']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/bonds/{secid}': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/bonds/{secid}/marketdata": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить рыночные данные облигации */ - get: operations["BondsController_getMarketData"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Получить спецификацию облигации */ + get: operations['BondsController_getBond']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/bonds/{secid}/marketdata': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/bonds/{secid}/history": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить дневную историю торгов облигации */ - get: operations["BondsController_getHistory"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Получить рыночные данные облигации */ + get: operations['BondsController_getMarketData']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/bonds/{secid}/history': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/shares/{secid}/candles": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить свечи акции */ - get: operations["CandlesController_getShareCandles"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Получить дневную историю торгов облигации */ + get: operations['BondsController_getHistory']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/shares/{secid}/candles': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - "/api/v1/securities/bonds/{secid}/candles": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - /** Получить свечи облигации */ - get: operations["CandlesController_getBondCandles"]; - put?: never; - post?: never; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; + /** Получить свечи акции */ + get: operations['CandlesController_getShareCandles']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + '/api/v1/securities/bonds/{secid}/candles': { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; + /** Получить свечи облигации */ + get: operations['CandlesController_getBondCandles']; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components { - schemas: never; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + schemas: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - HealthController_check: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; + HealthController_check: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - SecuritiesController_search: { - parameters: { - query: { - /** @description Поисковый запрос (тикер, название, ISIN) */ - q: string; - type?: "all" | "share" | "bond"; - limit?: number; - }; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; + content?: never; + }; }; - SharesController_getShare: { - parameters: { - query?: never; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; + }; + SecuritiesController_search: { + parameters: { + query: { + /** @description Поисковый запрос (тикер, название, ISIN) */ + q: string; + type?: 'all' | 'share' | 'bond'; + limit?: number; + }; + header?: never; + path?: never; + cookie?: never; }; - SharesController_getMarketData: { - parameters: { - query?: never; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; + content?: never; + }; }; - SharesController_getDividends: { - parameters: { - query?: never; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; + }; + SharesController_getShare: { + parameters: { + query?: never; + header?: never; + path: { + secid: string; + }; + cookie?: never; }; - SharesController_getHistory: { - parameters: { - query: { - from: string; - till: string; - }; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; + content?: never; + }; }; - BondsController_getBond: { - parameters: { - query?: never; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; + }; + SharesController_getMarketData: { + parameters: { + query?: never; + header?: never; + path: { + secid: string; + }; + cookie?: never; }; - BondsController_getMarketData: { - parameters: { - query?: never; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; + content?: never; + }; }; - BondsController_getHistory: { - parameters: { - query: { - from: string; - till: string; - }; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; + }; + SharesController_getDividends: { + parameters: { + query?: never; + header?: never; + path: { + secid: string; + }; + cookie?: never; }; - CandlesController_getShareCandles: { - parameters: { - query: { - interval: "1h" | "24h"; - from: string; - till: string; - }; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; }; + content?: never; + }; }; - CandlesController_getBondCandles: { - parameters: { - query: { - interval: "1h" | "24h"; - from: string; - till: string; - }; - header?: never; - path: { - secid: string; - }; - cookie?: never; - }; - requestBody?: never; - responses: { - 200: { - headers: { - [name: string]: unknown; - }; - content?: never; - }; - }; + }; + SharesController_getHistory: { + parameters: { + query: { + from: string; + till: string; + }; + header?: never; + path: { + secid: string; + }; + cookie?: never; }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + BondsController_getBond: { + parameters: { + query?: never; + header?: never; + path: { + secid: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + BondsController_getMarketData: { + parameters: { + query?: never; + header?: never; + path: { + secid: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + BondsController_getHistory: { + parameters: { + query: { + from: string; + till: string; + }; + header?: never; + path: { + secid: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + CandlesController_getShareCandles: { + parameters: { + query: { + interval: '1h' | '24h'; + from: string; + till: string; + }; + header?: never; + path: { + secid: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + CandlesController_getBondCandles: { + parameters: { + query: { + interval: '1h' | '24h'; + from: string; + till: string; + }; + header?: never; + path: { + secid: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; } diff --git a/apps/frontend/src/components/BondDetails.tsx b/apps/frontend/src/components/BondDetails.tsx index 993fd58..ae3cf9f 100644 --- a/apps/frontend/src/components/BondDetails.tsx +++ b/apps/frontend/src/components/BondDetails.tsx @@ -15,22 +15,27 @@ export function BondDetails({ bond }: BondDetailsProps) { const md = bond.marketData; return ( -
+

{bond.shortName}

-
- {bond.isin} -
+
{bond.isin}
-
- {md.price.toFixed(2)}% -
+
{md.price.toFixed(2)}%
Номинал - {bond.faceValue.toLocaleString('ru-RU')} {bond.faceUnit} + + {bond.faceValue.toLocaleString('ru-RU')} {bond.faceUnit} +
Дата погашения @@ -38,7 +43,9 @@ export function BondDetails({ bond }: BondDetailsProps) {
Купон - {md.couponValue} ₽ {md.couponPercent != null ? `(${md.couponPercent}%)` : ''} + + {md.couponValue} ₽ {md.couponPercent != null ? `(${md.couponPercent}%)` : ''} +
Период купона diff --git a/apps/frontend/src/components/Layout.tsx b/apps/frontend/src/components/Layout.tsx index f007e0e..3047715 100644 --- a/apps/frontend/src/components/Layout.tsx +++ b/apps/frontend/src/components/Layout.tsx @@ -14,7 +14,15 @@ export function Layout() { gap: 24, }} > - + MoexVibe diff --git a/apps/frontend/src/components/SearchBar.tsx b/apps/frontend/src/components/SearchBar.tsx index b97c2ef..5c30b7e 100644 --- a/apps/frontend/src/components/SearchBar.tsx +++ b/apps/frontend/src/components/SearchBar.tsx @@ -32,7 +32,10 @@ export function SearchBar() { type="text" placeholder="Поиск акций и облигаций..." value={query} - onChange={e => { setQuery(e.target.value); setOpen(true); }} + onChange={(e) => { + setQuery(e.target.value); + setOpen(true); + }} onFocus={() => setOpen(true)} style={{ width: '100%', @@ -66,13 +69,15 @@ export function SearchBar() {
  • Ничего не найдено
  • )} {!isLoading && - results?.map(item => ( + results?.map((item) => (
  • { setOpen(false); setQuery(''); - navigate(item.type === 'share' ? `/stocks/${item.secid}` : `/bonds/${item.secid}`); + navigate( + item.type === 'share' ? `/stocks/${item.secid}` : `/bonds/${item.secid}`, + ); }} style={{ padding: '10px 12px', @@ -82,14 +87,16 @@ export function SearchBar() { justifyContent: 'space-between', alignItems: 'center', }} - onMouseEnter={e => (e.currentTarget.style.background = '#f5f5f5')} - onMouseLeave={e => (e.currentTarget.style.background = '')} + onMouseEnter={(e) => (e.currentTarget.style.background = '#f5f5f5')} + onMouseLeave={(e) => (e.currentTarget.style.background = '')} > {item.shortName} {item.secid} - + {item.type === 'share' ? 'Акция' : 'Облигация'}
  • diff --git a/apps/frontend/src/components/StockDetails.tsx b/apps/frontend/src/components/StockDetails.tsx index d9423ee..2f4a177 100644 --- a/apps/frontend/src/components/StockDetails.tsx +++ b/apps/frontend/src/components/StockDetails.tsx @@ -16,7 +16,14 @@ export function StockDetails({ stock }: StockDetailsProps) { const isPositive = md.change >= 0; return ( -
    +

    {stock.shortName} ({stock.secid}) @@ -28,8 +35,14 @@ export function StockDetails({ stock }: StockDetailsProps) {
    {md.price.toLocaleString('ru-RU', { minimumFractionDigits: 2 })}{' '} - - {isPositive ? '+' : ''}{md.change.toFixed(2)} ({md.changePercent.toFixed(2)}%) + + {isPositive ? '+' : ''} + {md.change.toFixed(2)} ({md.changePercent.toFixed(2)}%)
    @@ -53,9 +66,7 @@ export function StockDetails({ stock }: StockDetailsProps) {
    Капитализация - {md.issueCapitalization - ? (md.issueCapitalization / 1e9).toFixed(2) + ' млрд ₽' - : '—'} + {md.issueCapitalization ? (md.issueCapitalization / 1e9).toFixed(2) + ' млрд ₽' : '—'}
    diff --git a/apps/frontend/src/pages/BondPage.tsx b/apps/frontend/src/pages/BondPage.tsx index c6ef913..9fba2c8 100644 --- a/apps/frontend/src/pages/BondPage.tsx +++ b/apps/frontend/src/pages/BondPage.tsx @@ -18,7 +18,14 @@ export function BondPage() {
    -
    +

    График цены

    diff --git a/apps/frontend/src/pages/HomePage.tsx b/apps/frontend/src/pages/HomePage.tsx index aa46098..a0f2c52 100644 --- a/apps/frontend/src/pages/HomePage.tsx +++ b/apps/frontend/src/pages/HomePage.tsx @@ -1,15 +1,11 @@ export function HomePage() { return (
    -

    - MoexVibe -

    +

    MoexVibe

    Анализ акций и облигаций Московской биржи

    -

    - Введите название или тикер в строку поиска выше -

    +

    Введите название или тикер в строку поиска выше

    Данные задерживаются на 15 минут · Бесплатный API MOEX ISS

    diff --git a/apps/frontend/src/pages/StockPage.tsx b/apps/frontend/src/pages/StockPage.tsx index f7b44c0..cff6179 100644 --- a/apps/frontend/src/pages/StockPage.tsx +++ b/apps/frontend/src/pages/StockPage.tsx @@ -20,13 +20,27 @@ export function StockPage() {
    -
    +

    График цены

    {dividends && dividends.length > 0 && ( -
    +

    Дивиденды