refactor(frontend): migrate entities/stock to FSD
This commit is contained in:
parent
93d18f1218
commit
dd727f9fb2
@ -5,13 +5,8 @@ export {
|
|||||||
setOnUnauthorized,
|
setOnUnauthorized,
|
||||||
getHealth,
|
getHealth,
|
||||||
searchSecurities,
|
searchSecurities,
|
||||||
getShare,
|
|
||||||
getShareMarketData,
|
|
||||||
getShareDividends,
|
|
||||||
getShareHistory,
|
|
||||||
getBond,
|
getBond,
|
||||||
getBondMarketData,
|
getBondMarketData,
|
||||||
getBondHistory,
|
getBondHistory,
|
||||||
getShareCandles,
|
|
||||||
getBondCandles,
|
getBondCandles,
|
||||||
} from '../shared/api/client';
|
} from '../shared/api/client';
|
||||||
|
|||||||
53
apps/frontend/src/entities/stock/api/stockApi.ts
Normal file
53
apps/frontend/src/entities/stock/api/stockApi.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { request } from '@/shared/api/client';
|
||||||
|
import type {
|
||||||
|
ApiResponseMeta,
|
||||||
|
ShareResponse,
|
||||||
|
StockMarketData,
|
||||||
|
DividendItem,
|
||||||
|
ShareHistoryItem,
|
||||||
|
CandleItem,
|
||||||
|
} from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function getShare(secid: string): Promise<{ data: ShareResponse; meta: ApiResponseMeta }> {
|
||||||
|
return request<ShareResponse>(`/api/v1/securities/shares/${encodeURIComponent(secid)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getShareMarketData(
|
||||||
|
secid: string,
|
||||||
|
): Promise<{ data: StockMarketData; meta: ApiResponseMeta }> {
|
||||||
|
return request<StockMarketData>(
|
||||||
|
`/api/v1/securities/shares/${encodeURIComponent(secid)}/marketdata`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getShareDividends(
|
||||||
|
secid: string,
|
||||||
|
): Promise<{ data: DividendItem[]; meta: ApiResponseMeta }> {
|
||||||
|
return request<DividendItem[]>(
|
||||||
|
`/api/v1/securities/shares/${encodeURIComponent(secid)}/dividends`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getShareHistory(
|
||||||
|
secid: string,
|
||||||
|
from: string,
|
||||||
|
till: string,
|
||||||
|
): Promise<{ data: ShareHistoryItem[]; meta: ApiResponseMeta }> {
|
||||||
|
return request<ShareHistoryItem[]>(
|
||||||
|
`/api/v1/securities/shares/${encodeURIComponent(secid)}/history`,
|
||||||
|
{ from, till },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getShareCandles(
|
||||||
|
secid: string,
|
||||||
|
interval: '1h' | '24h',
|
||||||
|
from: string,
|
||||||
|
till: string,
|
||||||
|
): Promise<{ data: CandleItem[]; meta: ApiResponseMeta }> {
|
||||||
|
return request<CandleItem[]>(`/api/v1/securities/shares/${encodeURIComponent(secid)}/candles`, {
|
||||||
|
interval,
|
||||||
|
from,
|
||||||
|
till,
|
||||||
|
});
|
||||||
|
}
|
||||||
10
apps/frontend/src/entities/stock/index.ts
Normal file
10
apps/frontend/src/entities/stock/index.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
export { useStock } from './model/useStock';
|
||||||
|
export { useStockCandles } from './model/useStockCandles';
|
||||||
|
export { useStockDividends } from './model/useStockDividends';
|
||||||
|
export {
|
||||||
|
getShare,
|
||||||
|
getShareMarketData,
|
||||||
|
getShareDividends,
|
||||||
|
getShareHistory,
|
||||||
|
getShareCandles,
|
||||||
|
} from './api/stockApi';
|
||||||
14
apps/frontend/src/entities/stock/model/useStock.ts
Normal file
14
apps/frontend/src/entities/stock/model/useStock.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getShare } from '../api/stockApi';
|
||||||
|
import type { ShareResponse } from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function useStock(secid: string) {
|
||||||
|
return useQuery<ShareResponse>({
|
||||||
|
queryKey: ['stock', secid],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await getShare(secid);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
staleTime: 900_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|||||||
import { renderHook, waitFor } from '@testing-library/react';
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import { server } from '../test/server';
|
import { server } from '../../../test/server';
|
||||||
import { useStockCandles } from './useStockCandles';
|
import { useStockCandles } from './useStockCandles';
|
||||||
import { type ReactNode } from 'react';
|
import { type ReactNode } from 'react';
|
||||||
|
|
||||||
14
apps/frontend/src/entities/stock/model/useStockCandles.ts
Normal file
14
apps/frontend/src/entities/stock/model/useStockCandles.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getShareCandles } from '../api/stockApi';
|
||||||
|
import type { CandleItem } from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function useStockCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
|
||||||
|
return useQuery<CandleItem[]>({
|
||||||
|
queryKey: ['stockCandles', secid, interval, from, till],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await getShareCandles(secid, interval, from, till);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
staleTime: 3600_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import { describe, it, expect } from 'vitest';
|
|||||||
import { renderHook, waitFor } from '@testing-library/react';
|
import { renderHook, waitFor } from '@testing-library/react';
|
||||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
import { http, HttpResponse } from 'msw';
|
import { http, HttpResponse } from 'msw';
|
||||||
import { server } from '../test/server';
|
import { server } from '../../../test/server';
|
||||||
import { useStockDividends } from './useStockDividends';
|
import { useStockDividends } from './useStockDividends';
|
||||||
import { type ReactNode } from 'react';
|
import { type ReactNode } from 'react';
|
||||||
|
|
||||||
14
apps/frontend/src/entities/stock/model/useStockDividends.ts
Normal file
14
apps/frontend/src/entities/stock/model/useStockDividends.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { useQuery } from '@tanstack/react-query';
|
||||||
|
import { getShareDividends } from '../api/stockApi';
|
||||||
|
import type { DividendItem } from '@/shared/api/responses';
|
||||||
|
|
||||||
|
export function useStockDividends(secid: string) {
|
||||||
|
return useQuery<DividendItem[]>({
|
||||||
|
queryKey: ['stockDividends', secid],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await getShareDividends(secid);
|
||||||
|
return res.data;
|
||||||
|
},
|
||||||
|
staleTime: 86400_000,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,14 +1 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
export { useStock } from '../entities/stock/model/useStock';
|
||||||
import { getShare } from '@/shared/api/client';
|
|
||||||
import type { ShareResponse } from '@/shared/api/responses';
|
|
||||||
|
|
||||||
export function useStock(secid: string) {
|
|
||||||
return useQuery<ShareResponse>({
|
|
||||||
queryKey: ['stock', secid],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await getShare(secid);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
staleTime: 900_000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,14 +1 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
export { useStockCandles } from '../entities/stock/model/useStockCandles';
|
||||||
import { getShareCandles } from '@/shared/api/client';
|
|
||||||
import type { CandleItem } from '@/shared/api/responses';
|
|
||||||
|
|
||||||
export function useStockCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
|
|
||||||
return useQuery<CandleItem[]>({
|
|
||||||
queryKey: ['stockCandles', secid, interval, from, till],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await getShareCandles(secid, interval, from, till);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
staleTime: 3600_000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,14 +1 @@
|
|||||||
import { useQuery } from '@tanstack/react-query';
|
export { useStockDividends } from '../entities/stock/model/useStockDividends';
|
||||||
import { getShareDividends } from '@/shared/api/client';
|
|
||||||
import type { DividendItem } from '@/shared/api/responses';
|
|
||||||
|
|
||||||
export function useStockDividends(secid: string) {
|
|
||||||
return useQuery<DividendItem[]>({
|
|
||||||
queryKey: ['stockDividends', secid],
|
|
||||||
queryFn: async () => {
|
|
||||||
const res = await getShareDividends(secid);
|
|
||||||
return res.data;
|
|
||||||
},
|
|
||||||
staleTime: 86400_000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { useStock } from '../hooks/useStock';
|
import { useStock, useStockCandles, useStockDividends } from '../entities/stock';
|
||||||
import { useStockCandles } from '../hooks/useStockCandles';
|
|
||||||
import { useStockDividends } from '../hooks/useStockDividends';
|
|
||||||
import { StockDetails } from '../components/StockDetails';
|
import { StockDetails } from '../components/StockDetails';
|
||||||
import { PriceChart } from '../components/PriceChart';
|
import { PriceChart } from '../components/PriceChart';
|
||||||
|
|
||||||
|
|||||||
@ -2,10 +2,6 @@ import type {
|
|||||||
ApiEnvelope,
|
ApiEnvelope,
|
||||||
ApiResponseMeta,
|
ApiResponseMeta,
|
||||||
AuthResponse,
|
AuthResponse,
|
||||||
ShareResponse,
|
|
||||||
StockMarketData,
|
|
||||||
DividendItem,
|
|
||||||
ShareHistoryItem,
|
|
||||||
BondResponse,
|
BondResponse,
|
||||||
BondMarketData,
|
BondMarketData,
|
||||||
BondHistoryItem,
|
BondHistoryItem,
|
||||||
@ -151,37 +147,6 @@ export function searchSecurities(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShare(secid: string): Promise<{ data: ShareResponse; meta: ApiResponseMeta }> {
|
|
||||||
return request<ShareResponse>(`/api/v1/securities/shares/${encodeURIComponent(secid)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShareMarketData(
|
|
||||||
secid: string,
|
|
||||||
): Promise<{ data: StockMarketData; meta: ApiResponseMeta }> {
|
|
||||||
return request<StockMarketData>(
|
|
||||||
`/api/v1/securities/shares/${encodeURIComponent(secid)}/marketdata`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShareDividends(
|
|
||||||
secid: string,
|
|
||||||
): Promise<{ data: DividendItem[]; meta: ApiResponseMeta }> {
|
|
||||||
return request<DividendItem[]>(
|
|
||||||
`/api/v1/securities/shares/${encodeURIComponent(secid)}/dividends`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShareHistory(
|
|
||||||
secid: string,
|
|
||||||
from: string,
|
|
||||||
till: string,
|
|
||||||
): Promise<{ data: ShareHistoryItem[]; meta: ApiResponseMeta }> {
|
|
||||||
return request<ShareHistoryItem[]>(
|
|
||||||
`/api/v1/securities/shares/${encodeURIComponent(secid)}/history`,
|
|
||||||
{ from, till },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBond(secid: string): Promise<{ data: BondResponse; meta: ApiResponseMeta }> {
|
export function getBond(secid: string): Promise<{ data: BondResponse; meta: ApiResponseMeta }> {
|
||||||
return request<BondResponse>(`/api/v1/securities/bonds/${encodeURIComponent(secid)}`);
|
return request<BondResponse>(`/api/v1/securities/bonds/${encodeURIComponent(secid)}`);
|
||||||
}
|
}
|
||||||
@ -205,19 +170,6 @@ export function getBondHistory(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShareCandles(
|
|
||||||
secid: string,
|
|
||||||
interval: '1h' | '24h',
|
|
||||||
from: string,
|
|
||||||
till: string,
|
|
||||||
): Promise<{ data: CandleItem[]; meta: ApiResponseMeta }> {
|
|
||||||
return request<CandleItem[]>(`/api/v1/securities/shares/${encodeURIComponent(secid)}/candles`, {
|
|
||||||
interval,
|
|
||||||
from,
|
|
||||||
till,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getBondCandles(
|
export function getBondCandles(
|
||||||
secid: string,
|
secid: string,
|
||||||
interval: '1h' | '24h',
|
interval: '1h' | '24h',
|
||||||
|
|||||||
@ -5,14 +5,9 @@ export {
|
|||||||
setOnUnauthorized,
|
setOnUnauthorized,
|
||||||
getHealth,
|
getHealth,
|
||||||
searchSecurities,
|
searchSecurities,
|
||||||
getShare,
|
|
||||||
getShareMarketData,
|
|
||||||
getShareDividends,
|
|
||||||
getShareHistory,
|
|
||||||
getBond,
|
getBond,
|
||||||
getBondMarketData,
|
getBondMarketData,
|
||||||
getBondHistory,
|
getBondHistory,
|
||||||
getShareCandles,
|
|
||||||
getBondCandles,
|
getBondCandles,
|
||||||
} from './client';
|
} from './client';
|
||||||
export type {
|
export type {
|
||||||
|
|||||||
178
docs/features/fsd-entities-migration/plan.md
Normal file
178
docs/features/fsd-entities-migration/plan.md
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
# FSD Entities Migration — Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [x]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Перенести stock, bond и portfolio домены в FSD-сущности (`entities/`) без изменения пользовательского поведения.
|
||||||
|
|
||||||
|
**Architecture:** Миграция по паттерну broker-пилота: создаётся FSD-каркас entity → код переносится → старые файлы становятся shim-ами → потребители переключаются на новые импорты → общий клиент очищается.
|
||||||
|
|
||||||
|
**Tech Stack:** React 18, TypeScript, React Router v6, TanStack Query v5, Vitest, Testing Library.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Карта файлов
|
||||||
|
|
||||||
|
### Stock entity
|
||||||
|
|
||||||
|
- Create `apps/frontend/src/entities/stock/index.ts`
|
||||||
|
- Create `apps/frontend/src/entities/stock/api/stockApi.ts`
|
||||||
|
- Create `apps/frontend/src/entities/stock/model/useStock.ts`
|
||||||
|
- Create `apps/frontend/src/entities/stock/model/useStockCandles.ts`
|
||||||
|
- Create `apps/frontend/src/entities/stock/model/useStockDividends.ts`
|
||||||
|
|
||||||
|
### Bond entity
|
||||||
|
|
||||||
|
- Create `apps/frontend/src/entities/bond/index.ts`
|
||||||
|
- Create `apps/frontend/src/entities/bond/api/bondApi.ts`
|
||||||
|
- Create `apps/frontend/src/entities/bond/model/useBond.ts`
|
||||||
|
- Create `apps/frontend/src/entities/bond/model/useBondCandles.ts`
|
||||||
|
|
||||||
|
### Portfolio entity
|
||||||
|
|
||||||
|
- Create `apps/frontend/src/entities/portfolio/index.ts`
|
||||||
|
- Create `apps/frontend/src/entities/portfolio/api/portfolioApi.ts`
|
||||||
|
- Create `apps/frontend/src/entities/portfolio/model/usePortfolio.ts`
|
||||||
|
- Create `apps/frontend/src/entities/portfolio/model/usePortfolios.ts`
|
||||||
|
- Create `apps/frontend/src/entities/portfolio/model/usePortfolioAnalytics.ts`
|
||||||
|
- Create `apps/frontend/src/entities/portfolio/model/usePortfolioMutations.ts`
|
||||||
|
- Create `apps/frontend/src/entities/portfolio/model/usePositionMutations.ts`
|
||||||
|
|
||||||
|
### Shared cleanup
|
||||||
|
|
||||||
|
- Modify `apps/frontend/src/shared/api/client.ts` — удалить 9 stock/bond API-функций
|
||||||
|
- Modify `apps/frontend/src/shared/api/index.ts` — удалить re-exports stock/bond функций
|
||||||
|
- Modify `apps/frontend/src/api/client.ts` — удалить re-exports stock/bond функций (shim)
|
||||||
|
|
||||||
|
### Consumer migration
|
||||||
|
|
||||||
|
- Modify `apps/frontend/src/pages/StockPage.tsx` — переключить импорты на `../../entities/stock`
|
||||||
|
- Modify `apps/frontend/src/pages/BondPage.tsx` — переключить импорты на `../../entities/bond`
|
||||||
|
- Modify `apps/frontend/src/pages/portfolios/PortfolioDetailPage.tsx` — на `../../entities/portfolio`
|
||||||
|
- Modify `apps/frontend/src/pages/portfolios/PortfoliosListPage.tsx` — на `../../entities/portfolio`
|
||||||
|
|
||||||
|
### Shim files (обратно-совместимые re-export)
|
||||||
|
|
||||||
|
- Modify `apps/frontend/src/hooks/useStock.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/useStockCandles.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/useStockDividends.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/useBond.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/useBondCandles.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/usePortfolio.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/usePortfolios.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/usePortfolioAnalytics.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/usePortfolioMutations.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/hooks/usePositionMutations.ts` → shim
|
||||||
|
- Modify `apps/frontend/src/api/portfolio.ts` → shim
|
||||||
|
|
||||||
|
### Test relocation
|
||||||
|
|
||||||
|
- Move `apps/frontend/src/hooks/useStock.test.tsx` → `apps/frontend/src/entities/stock/model/useStock.test.tsx`
|
||||||
|
- Move `apps/frontend/src/hooks/useStockCandles.test.tsx` → `apps/frontend/src/entities/stock/model/useStockCandles.test.tsx`
|
||||||
|
- Move `apps/frontend/src/hooks/useStockDividends.test.tsx` → `apps/frontend/src/entities/stock/model/useStockDividends.test.tsx`
|
||||||
|
- Move `apps/frontend/src/hooks/useBond.test.tsx` → `apps/frontend/src/entities/bond/model/useBond.test.tsx`
|
||||||
|
- Move `apps/frontend/src/hooks/useBondCandles.test.tsx` → `apps/frontend/src/entities/bond/model/useBondCandles.test.tsx`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 1: entities/stock/
|
||||||
|
|
||||||
|
### Файлы
|
||||||
|
|
||||||
|
- Create: `src/entities/stock/index.ts`
|
||||||
|
- Create: `src/entities/stock/api/stockApi.ts`
|
||||||
|
- Create: `src/entities/stock/model/useStock.ts`
|
||||||
|
- Create: `src/entities/stock/model/useStockCandles.ts`
|
||||||
|
- Create: `src/entities/stock/model/useStockDividends.ts`
|
||||||
|
- Move: `src/hooks/useStock.test.tsx` → `src/entities/stock/model/useStock.test.tsx`
|
||||||
|
- Move: `src/hooks/useStockCandles.test.tsx` → `src/entities/stock/model/useStockCandles.test.tsx`
|
||||||
|
- Move: `src/hooks/useStockDividends.test.tsx` → `src/entities/stock/model/useStockDividends.test.tsx`
|
||||||
|
|
||||||
|
### Связанные изменения
|
||||||
|
|
||||||
|
- Modify: `src/shared/api/client.ts` — удалить `getShare`, `getShareMarketData`, `getShareDividends`, `getShareHistory`, `getShareCandles`
|
||||||
|
- Modify: `src/shared/api/index.ts` — удалить re-exports stock функций
|
||||||
|
- Modify: `src/api/client.ts` — удалить re-exports stock функций
|
||||||
|
- Modify: `src/hooks/useStock.ts` — заменить на shim
|
||||||
|
- Modify: `src/hooks/useStockCandles.ts` — заменить на shim
|
||||||
|
- Modify: `src/hooks/useStockDividends.ts` — заменить на shim
|
||||||
|
- Modify: `src/pages/StockPage.tsx` — переключить импорты
|
||||||
|
|
||||||
|
- [ ] **Step 1: Создать api/stockApi.ts с функциями запросов stock**
|
||||||
|
- [ ] **Step 2: Создать model хуки для stock (useStock, useStockCandles, useStockDividends)**
|
||||||
|
- [ ] **Step 3: Создать entities/stock/index.ts barrel**
|
||||||
|
- [ ] **Step 4: Переместить тесты stock в model/ и обновить импорты**
|
||||||
|
- [ ] **Step 5: Удалить stock функции из shared/api/client.ts и обновить barrely (shared/api/index.ts, api/client.ts)**
|
||||||
|
- [ ] **Step 6: Превратить hooks/useStock* в shim-файлы**
|
||||||
|
- [ ] **Step 7: Переключить StockPage.tsx на импорты из ../../entities/stock**
|
||||||
|
- [ ] **Step 8: Запустить тесты: npm test -w apps/frontend -- useStock**
|
||||||
|
- [ ] **Step 9: Закоммитить**
|
||||||
|
|
||||||
|
## Task 2: entities/bond/
|
||||||
|
|
||||||
|
### Файлы
|
||||||
|
|
||||||
|
- Create: `src/entities/bond/index.ts`
|
||||||
|
- Create: `src/entities/bond/api/bondApi.ts`
|
||||||
|
- Create: `src/entities/bond/model/useBond.ts`
|
||||||
|
- Create: `src/entities/bond/model/useBondCandles.ts`
|
||||||
|
- Move: `src/hooks/useBond.test.tsx` → `src/entities/bond/model/useBond.test.tsx`
|
||||||
|
- Move: `src/hooks/useBondCandles.test.tsx` → `src/entities/bond/model/useBondCandles.test.tsx`
|
||||||
|
|
||||||
|
### Связанные изменения
|
||||||
|
|
||||||
|
- Modify: `src/shared/api/client.ts` — удалить `getBond`, `getBondMarketData`, `getBondHistory`, `getBondCandles`
|
||||||
|
- Modify: `src/shared/api/index.ts` — удалить re-exports bond функций
|
||||||
|
- Modify: `src/api/client.ts` — удалить re-exports bond функций
|
||||||
|
- Modify: `src/hooks/useBond.ts` — заменить на shim
|
||||||
|
- Modify: `src/hooks/useBondCandles.ts` — заменить на shim
|
||||||
|
- Modify: `src/pages/BondPage.tsx` — переключить импорты
|
||||||
|
|
||||||
|
- [ ] **Step 1: Создать api/bondApi.ts с функциями запросов bond**
|
||||||
|
- [ ] **Step 2: Создать model хуки для bond (useBond, useBondCandles)**
|
||||||
|
- [ ] **Step 3: Создать entities/bond/index.ts barrel**
|
||||||
|
- [ ] **Step 4: Переместить тесты bond в model/ и обновить импорты**
|
||||||
|
- [ ] **Step 5: Удалить bond функции из shared/api/client.ts и обновить barrely**
|
||||||
|
- [ ] **Step 6: Превратить hooks/useBond* в shim-файлы**
|
||||||
|
- [ ] **Step 7: Переключить BondPage.tsx на импорты из ../../entities/bond**
|
||||||
|
- [ ] **Step 8: Запустить тесты: npm test -w apps/frontend -- useBond**
|
||||||
|
- [ ] **Step 9: Закоммитить**
|
||||||
|
|
||||||
|
## Task 3: entities/portfolio/
|
||||||
|
|
||||||
|
### Файлы
|
||||||
|
|
||||||
|
- Create: `src/entities/portfolio/index.ts`
|
||||||
|
- Create: `src/entities/portfolio/api/portfolioApi.ts`
|
||||||
|
- Create: `src/entities/portfolio/model/usePortfolio.ts`
|
||||||
|
- Create: `src/entities/portfolio/model/usePortfolios.ts`
|
||||||
|
- Create: `src/entities/portfolio/model/usePortfolioAnalytics.ts`
|
||||||
|
- Create: `src/entities/portfolio/model/usePortfolioMutations.ts`
|
||||||
|
- Create: `src/entities/portfolio/model/usePositionMutations.ts`
|
||||||
|
|
||||||
|
### Связанные изменения
|
||||||
|
|
||||||
|
- Modify: `src/api/portfolio.ts` — заменить на shim (re-export из entities/portfolio/api/portfolioApi)
|
||||||
|
- Modify: `src/hooks/usePortfolio.ts` — заменить на shim
|
||||||
|
- Modify: `src/hooks/usePortfolios.ts` — заменить на shim
|
||||||
|
- Modify: `src/hooks/usePortfolioAnalytics.ts` — заменить на shim
|
||||||
|
- Modify: `src/hooks/usePortfolioMutations.ts` — заменить на shim
|
||||||
|
- Modify: `src/hooks/usePositionMutations.ts` — заменить на shim
|
||||||
|
- Modify: `src/pages/portfolios/PortfolioDetailPage.tsx` — переключить импорты
|
||||||
|
- Modify: `src/pages/portfolios/PortfoliosListPage.tsx` — переключить импорты
|
||||||
|
|
||||||
|
- [ ] **Step 1: Создать api/portfolioApi.ts — перенести все функции из api/portfolio.ts**
|
||||||
|
- [ ] **Step 2: Создать model хуки для portfolio (5 файлов)**
|
||||||
|
- [ ] **Step 3: Создать entities/portfolio/index.ts barrel**
|
||||||
|
- [ ] **Step 4: Превратить api/portfolio.ts в shim**
|
||||||
|
- [ ] **Step 5: Превратить hooks/usePortfolio* и usePositionMutations в shim-файлы**
|
||||||
|
- [ ] **Step 6: Переключить PortfolioDetailPage.tsx и PortfoliosListPage.tsx на импорты из ../../entities/portfolio**
|
||||||
|
- [ ] **Step 7: Запустить build: npm run build -w apps/frontend**
|
||||||
|
- [ ] **Step 8: Закоммитить**
|
||||||
|
|
||||||
|
## Task 4: Финальная проверка
|
||||||
|
|
||||||
|
- [ ] **Step 1: Полный прогон тестов: npm test -w apps/frontend**
|
||||||
|
- [ ] **Step 2: Линт: npm run lint -w apps/frontend**
|
||||||
|
- [ ] **Step 3: Сборка: npm run build -w apps/frontend**
|
||||||
|
- [ ] **Step 4: Проверить git diff --stat на предмет только целевых изменений**
|
||||||
|
- [ ] **Step 5: Обновить docs/features/fsd-entities-migration/tasks.md**
|
||||||
81
docs/features/fsd-entities-migration/spec.md
Normal file
81
docs/features/fsd-entities-migration/spec.md
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# FSD Entities Migration
|
||||||
|
|
||||||
|
Дата: 2026-06-20
|
||||||
|
Статус: в работе
|
||||||
|
|
||||||
|
## Контекст
|
||||||
|
|
||||||
|
Продолжается поэтапный переход frontend на Feature-Sliced Design (FSD). После успешного пилота
|
||||||
|
broker-домена (`frontend-fsd-broker-pilot`) и создания shared-слоя (`frontend-fsd-shared-layer`)
|
||||||
|
следующий шаг — миграция core domain entities: stock, bond, portfolio.
|
||||||
|
|
||||||
|
В текущей структуре их код размазан по техническим каталогам:
|
||||||
|
- API-функции акций и облигаций находятся в `shared/api/client.ts` (вместе с HTTP-инфраструктурой)
|
||||||
|
- API-функции портфелей находятся в `api/portfolio.ts` (в legacy-папке)
|
||||||
|
- TanStack Query хуки находятся в `hooks/` (5 файлов для portfolio + 3 для stock + 2 для bond)
|
||||||
|
- Типы уже централизованы в `shared/api/responses.ts`
|
||||||
|
|
||||||
|
Это усложняет навигацию, поддержку и дальнейшее масштабирование FSD-архитектуры.
|
||||||
|
|
||||||
|
## Цель
|
||||||
|
|
||||||
|
Перенести stock, bond и portfolio домены в FSD-сущности (`entities/`) по тому же паттерну, что и
|
||||||
|
broker-account/broker-position/broker-operation:
|
||||||
|
|
||||||
|
- `entities/stock/` — API-функции и query-хуки для акций
|
||||||
|
- `entities/bond/` — API-функции и query-хуки для облигаций
|
||||||
|
- `entities/portfolio/` — API-функции и query-хуки для портфелей
|
||||||
|
|
||||||
|
## Требования
|
||||||
|
|
||||||
|
### 1. Каждая сущность получает явную FSD-структуру
|
||||||
|
|
||||||
|
- `api/` — чистые функции запросов (без TanStack Query, только `request` вызовы)
|
||||||
|
- `model/` — TanStack Query хуки и бизнес-логика
|
||||||
|
- `index.ts` — barrel export (только re-exports, без логики)
|
||||||
|
|
||||||
|
### 2. API-функции выносятся из shared/api/client.ts
|
||||||
|
|
||||||
|
`shared/api/client.ts` должен содержать только инфраструктуру:
|
||||||
|
- `request` — базовый HTTP-клиент
|
||||||
|
- `setAccessToken`, `getAccessToken`, `setOnUnauthorized` — управление токенами
|
||||||
|
- `searchSecurities` — поиск (кросс-доменный)
|
||||||
|
- `getHealth` — health check
|
||||||
|
|
||||||
|
Все stock/bond API-функции переезжают в соответствующие entity `api/`.
|
||||||
|
|
||||||
|
### 3. Query-хуки переносятся из global hooks/
|
||||||
|
|
||||||
|
Все TanStack Query хуки для stock, bond и portfolio переезжают из `hooks/` в
|
||||||
|
`entities/{entity}/model/`.
|
||||||
|
|
||||||
|
### 4. Совместимость на время перехода
|
||||||
|
|
||||||
|
Старые файлы становятся re-export шимами, чтобы избежать big-bang переписывания.
|
||||||
|
Потребители (страницы) обновляются на новые FSD-импорты в рамках этой же фичи.
|
||||||
|
|
||||||
|
### 5. Тесты следуют за кодом
|
||||||
|
|
||||||
|
Unit-тесты и hook-тесты перемещаются рядом с новым расположением кода.
|
||||||
|
|
||||||
|
### 6. Поведение не меняется
|
||||||
|
|
||||||
|
Никакой функциональности не добавляется и не изменяется. Только перегруппировка кода.
|
||||||
|
|
||||||
|
## Ограничения
|
||||||
|
|
||||||
|
- Не затрагиваются `features/` и `app/` слои — они будут в следующих итерациях
|
||||||
|
- Не затрагиваются `useSearch`, `useScreener`, `api/auth.ts`, `api/screener.ts`
|
||||||
|
- Не меняются `shared/api/responses.ts` и generated types
|
||||||
|
- Не меняются backend-контракты
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `entities/stock/` существует с api/model/index.ts, все тесты проходят
|
||||||
|
- `entities/bond/` существует с api/model/index.ts, все тесты проходят
|
||||||
|
- `entities/portfolio/` существует с api/model/index.ts
|
||||||
|
- `shared/api/client.ts` больше не содержит stock/bond API-функций
|
||||||
|
- Все страницы (StockPage, BondPage, PortfolioDetailPage, PortfoliosListPage) импортируют
|
||||||
|
из новых entity entrypoints
|
||||||
|
- Старые файлы стали re-export шимами
|
||||||
|
- Frontend lint, tests и build проходят
|
||||||
53
docs/features/fsd-entities-migration/tasks.md
Normal file
53
docs/features/fsd-entities-migration/tasks.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# FSD Entities Migration — задачи
|
||||||
|
|
||||||
|
Статус: запланировано
|
||||||
|
|
||||||
|
Подробные шаги, команды и expected results находятся в [plan.md](plan.md).
|
||||||
|
|
||||||
|
## Pre-flight
|
||||||
|
|
||||||
|
- [ ] Feature branch создана: `codex/fsd-entities-migration`
|
||||||
|
- [ ] spec.md написана
|
||||||
|
- [ ] plan.md написан
|
||||||
|
- [ ] tasks.md создан
|
||||||
|
- [ ] Все тесты проходят на текущем состоянии
|
||||||
|
|
||||||
|
## 1. entities/stock/
|
||||||
|
|
||||||
|
- [ ] Создать `entities/stock/api/stockApi.ts` с API-функциями stock
|
||||||
|
- [ ] Создать `entities/stock/model/useStock.ts`, `useStockCandles.ts`, `useStockDividends.ts`
|
||||||
|
- [ ] Создать `entities/stock/index.ts` barrel
|
||||||
|
- [ ] Переместить тесты в `entities/stock/model/`
|
||||||
|
- [ ] Удалить stock API из `shared/api/client.ts` и barrely
|
||||||
|
- [ ] Превратить `hooks/useStock*` в shim-файлы
|
||||||
|
- [ ] Переключить `StockPage.tsx` на новые импорты
|
||||||
|
- [ ] Тесты проходят
|
||||||
|
|
||||||
|
## 2. entities/bond/
|
||||||
|
|
||||||
|
- [ ] Создать `entities/bond/api/bondApi.ts` с API-функциями bond
|
||||||
|
- [ ] Создать `entities/bond/model/useBond.ts`, `useBondCandles.ts`
|
||||||
|
- [ ] Создать `entities/bond/index.ts` barrel
|
||||||
|
- [ ] Переместить тесты в `entities/bond/model/`
|
||||||
|
- [ ] Удалить bond API из `shared/api/client.ts` и barrely
|
||||||
|
- [ ] Превратить `hooks/useBond*` в shim-файлы
|
||||||
|
- [ ] Переключить `BondPage.tsx` на новые импорты
|
||||||
|
- [ ] Тесты проходят
|
||||||
|
|
||||||
|
## 3. entities/portfolio/
|
||||||
|
|
||||||
|
- [ ] Создать `entities/portfolio/api/portfolioApi.ts`
|
||||||
|
- [ ] Создать `entities/portfolio/model/` (5 хуков)
|
||||||
|
- [ ] Создать `entities/portfolio/index.ts` barrel
|
||||||
|
- [ ] Превратить `api/portfolio.ts` в shim
|
||||||
|
- [ ] Превратить `hooks/usePortfolio*` и `usePositionMutations` в shim-файлы
|
||||||
|
- [ ] Переключить страницы портфелей на новые импорты
|
||||||
|
- [ ] Build проходит
|
||||||
|
|
||||||
|
## 4. Финальная проверка
|
||||||
|
|
||||||
|
- [ ] Все тесты проходят
|
||||||
|
- [ ] Линт проходит
|
||||||
|
- [ ] Build проходит
|
||||||
|
- [ ] Только целевые изменения в diff
|
||||||
|
- [ ] Отмечено выполнение в tasks.md
|
||||||
Loading…
x
Reference in New Issue
Block a user