feat: complete Phase 3 API type unification, delete stale test file
- Delete shared/api/responses.ts, move type re-exports to index.ts - Update all 55+ imports from shared/api/responses to shared/api - Delete stale client.test.ts (tested deleted client.ts) - Run biome checks and fix import ordering - Update tasks.md and plan.md to reflect actual approach
This commit is contained in:
parent
063e80c375
commit
9932278b64
@ -6,8 +6,8 @@ import {
|
||||
handleUnauthorized,
|
||||
setOnUnauthorized,
|
||||
} from '@/entities/session/api/tokenManager'
|
||||
import type { UserResponse } from '@/shared/api'
|
||||
import { configureKyAuth } from '@/shared/api/kyClient'
|
||||
import type { UserResponse } from '@/shared/api/responses'
|
||||
|
||||
export function SessionProvider({ children }: { children: ReactNode }) {
|
||||
const [user, setUser] = useState<UserResponse | null>(null)
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type {
|
||||
ApiResponseMeta,
|
||||
BondHistoryItem,
|
||||
BondMarketData,
|
||||
BondResponse,
|
||||
CandleItem,
|
||||
} from '@/shared/api/responses'
|
||||
} from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
|
||||
export function getBond(secid: string): Promise<{ data: BondResponse; meta: ApiResponseMeta }> {
|
||||
return request<BondResponse>(`/api/v1/securities/bonds/${encodeURIComponent(secid)}`)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { BondResponse } from '@/shared/api/responses'
|
||||
import type { BondResponse } from '@/shared/api'
|
||||
import { getBond } from '../api/bondApi'
|
||||
|
||||
export function useBond(secid: string) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { CandleItem } from '@/shared/api/responses'
|
||||
import type { CandleItem } from '@/shared/api'
|
||||
import { getBondCandles } from '../api/bondApi'
|
||||
|
||||
export function useBondCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ApiResponseMeta, BrokerAccount, BrokerPortfolio } from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type { ApiResponseMeta, BrokerAccount, BrokerPortfolio } from '@/shared/api/responses'
|
||||
|
||||
export type BrokerOperationQuery = {
|
||||
from?: string
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerPortfolio } from '@/shared/api'
|
||||
import { aggregateBrokerAccounts } from '../model/brokerAccountsOverview'
|
||||
|
||||
function portfolio(
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api'
|
||||
|
||||
export interface BrokerCurrencyAllocationSummary {
|
||||
shares: number
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQueries } from '@tanstack/react-query'
|
||||
import type { BrokerAccount, BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerAccount, BrokerPortfolio } from '@/shared/api'
|
||||
import { getBrokerPortfolio } from '../api/brokerAccountApi'
|
||||
|
||||
export function useBrokerAccountPortfolios(accounts: BrokerAccount[]) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { BrokerAccount } from '@/shared/api/responses'
|
||||
import type { BrokerAccount } from '@/shared/api'
|
||||
import { getBrokerAccounts } from '../api/brokerAccountApi'
|
||||
|
||||
export function useBrokerAccounts() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerPortfolio } from '@/shared/api'
|
||||
import { getBrokerPortfolio } from '../api/brokerAccountApi'
|
||||
|
||||
export function useBrokerPortfolio(accountId: string | undefined) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ApiResponseMeta, BrokerEventsData } from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type { ApiResponseMeta, BrokerEventsData } from '@/shared/api/responses'
|
||||
|
||||
export type BrokerEventsQuery = {
|
||||
from: string
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { BrokerEventsData } from '@/shared/api/responses'
|
||||
import type { BrokerEventsData } from '@/shared/api'
|
||||
import { type BrokerEventsQuery, getBrokerEvents } from '../api/brokerEventApi'
|
||||
|
||||
export function useBrokerEvents(accountId: string | undefined, query: BrokerEventsQuery) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ApiResponseMeta, BrokerOperationsPage } from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type { ApiResponseMeta, BrokerOperationsPage } from '@/shared/api/responses'
|
||||
|
||||
export type BrokerOperationQuery = {
|
||||
from?: string
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { BrokerOperation } from '@/shared/api/responses'
|
||||
import type { BrokerOperation } from '@/shared/api'
|
||||
|
||||
export type BrokerOperationImpact = 'adds' | 'reduces' | 'neutral' | 'unknown'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
||||
import type { BrokerOperationsPage } from '@/shared/api/responses'
|
||||
import type { BrokerOperationsPage } from '@/shared/api'
|
||||
import { type BrokerOperationQuery, getBrokerOperations } from '../api/brokerOperationApi'
|
||||
|
||||
export function useBrokerOperations(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ApiResponseMeta, BrokerPositionsPage } from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type { ApiResponseMeta, BrokerPositionsPage } from '@/shared/api/responses'
|
||||
|
||||
export function getBrokerPositions(
|
||||
accountId: string,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api'
|
||||
import { buildBrokerAllocation } from './brokerAllocation'
|
||||
|
||||
function money(value: number): BrokerMoney {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerPortfolio } from '@/shared/api'
|
||||
|
||||
export type BrokerAllocationKey = 'shares' | 'bonds' | 'etf' | 'cash' | 'other'
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
getBrokerOperationTypeLabel,
|
||||
isBrokerOperationType,
|
||||
} from '@/entities/broker-operation'
|
||||
import type { BrokerOperation, BrokerPosition } from '@/shared/api/responses'
|
||||
import type { BrokerOperation, BrokerPosition } from '@/shared/api'
|
||||
import { getBrokerInstrumentPath, getBrokerPositionGroup } from './brokerDisplay'
|
||||
|
||||
function position(input: Partial<BrokerPosition>): BrokerPosition {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { BrokerPosition } from '@/shared/api/responses'
|
||||
import type { BrokerPosition } from '@/shared/api'
|
||||
|
||||
export type BrokerPositionGroup = 'shares' | 'bonds' | 'other'
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { keepPreviousData, useQuery } from '@tanstack/react-query'
|
||||
import type { BrokerPositionsPage } from '@/shared/api/responses'
|
||||
import type { BrokerPositionsPage } from '@/shared/api'
|
||||
import { getBrokerPositions } from '../api/brokerPositionApi'
|
||||
|
||||
export function useBrokerPositions(
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
import type { AnalyticsResponse, Portfolio, PortfolioDetail, Position } from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type {
|
||||
AnalyticsResponse,
|
||||
Portfolio,
|
||||
PortfolioDetail,
|
||||
Position,
|
||||
} from '@/shared/api/responses'
|
||||
|
||||
export function getPortfolios(): Promise<{
|
||||
data: Portfolio[]
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { PortfolioDetail } from '@/shared/api/responses'
|
||||
import type { PortfolioDetail } from '@/shared/api'
|
||||
import { getPortfolio } from '../api/portfolioApi'
|
||||
|
||||
export function usePortfolio(id: number) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { AnalyticsResponse } from '@/shared/api/responses'
|
||||
import type { AnalyticsResponse } from '@/shared/api'
|
||||
import { getPortfolioAnalytics } from '../api/portfolioApi'
|
||||
|
||||
export function usePortfolioAnalytics(portfolioId: number) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { Portfolio } from '@/shared/api/responses'
|
||||
import type { Portfolio } from '@/shared/api'
|
||||
import { getPortfolios } from '../api/portfolioApi'
|
||||
|
||||
export function usePortfolios() {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query'
|
||||
import type { PortfolioDetail } from '@/shared/api/responses'
|
||||
import type { PortfolioDetail } from '@/shared/api'
|
||||
import { addPosition, removePosition, updatePosition } from '../api/portfolioApi'
|
||||
|
||||
export function usePositionMutations(portfolioId: number) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { SearchResultItem } from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type { SearchResultItem } from '@/shared/api/responses'
|
||||
|
||||
export function searchSecurities(q: string, type: 'all' | 'share' | 'bond' = 'all', limit = 20) {
|
||||
return request<SearchResultItem[]>('/api/v1/securities/search', {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { SearchResultItem } from '@/shared/api/responses'
|
||||
import type { SearchResultItem } from '@/shared/api'
|
||||
import { searchSecurities } from '../api/searchApi'
|
||||
|
||||
export function useSearch(query: string) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { AuthResponse, UserResponse } from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type { AuthResponse, UserResponse } from '@/shared/api/responses'
|
||||
import { setAccessToken } from './tokenManager'
|
||||
|
||||
export async function login(email: string, password: string) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { AuthResponse } from '@/shared/api'
|
||||
import { normalizeEnvelope } from '@/shared/api/kyClient'
|
||||
import type { AuthResponse } from '@/shared/api/responses'
|
||||
|
||||
let accessToken: string | null = null
|
||||
let onUnauthorized: (() => void) | null = null
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { create } from 'zustand'
|
||||
import type { UserResponse } from '@/shared/api/responses'
|
||||
import type { UserResponse } from '@/shared/api'
|
||||
|
||||
interface SessionState {
|
||||
user: UserResponse | null
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type {
|
||||
ApiResponseMeta,
|
||||
CandleItem,
|
||||
@ -6,7 +5,8 @@ import type {
|
||||
ShareHistoryItem,
|
||||
ShareResponse,
|
||||
StockMarketData,
|
||||
} from '@/shared/api/responses'
|
||||
} from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
|
||||
export function getShare(secid: string): Promise<{ data: ShareResponse; meta: ApiResponseMeta }> {
|
||||
return request<ShareResponse>(`/api/v1/securities/shares/${encodeURIComponent(secid)}`)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { ShareResponse } from '@/shared/api/responses'
|
||||
import type { ShareResponse } from '@/shared/api'
|
||||
import { getShare } from '../api/stockApi'
|
||||
|
||||
export function useStock(secid: string) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { CandleItem } from '@/shared/api/responses'
|
||||
import type { CandleItem } from '@/shared/api'
|
||||
import { getShareCandles } from '../api/stockApi'
|
||||
|
||||
export function useStockCandles(secid: string, interval: '1h' | '24h', from: string, till: string) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import type { DividendItem } from '@/shared/api/responses'
|
||||
import type { DividendItem } from '@/shared/api'
|
||||
import { getShareDividends } from '../api/stockApi'
|
||||
|
||||
export function useStockDividends(secid: string) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type { ScreenerResult } from '@/shared/api'
|
||||
import { request } from '@/shared/api/kyClient'
|
||||
import type { ScreenerResult } from '@/shared/api/responses'
|
||||
|
||||
export interface ScreenerQuery {
|
||||
type: 'share' | 'bond'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import type { ScreenerResult } from '@/shared/api/responses'
|
||||
import type { ScreenerResult } from '@/shared/api'
|
||||
|
||||
interface Props {
|
||||
result: ScreenerResult
|
||||
|
||||
@ -1,163 +0,0 @@
|
||||
import { HttpResponse, http } from 'msw'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import {
|
||||
getAccessToken,
|
||||
handleUnauthorized,
|
||||
setAccessToken,
|
||||
setOnUnauthorized,
|
||||
} from '@/entities/session/api/tokenManager'
|
||||
import { server } from '@/shared/lib/test/server'
|
||||
import { configureKyAuth, request } from './kyClient'
|
||||
|
||||
const API = '/api/v1'
|
||||
|
||||
beforeEach(() => {
|
||||
setAccessToken(null)
|
||||
configureKyAuth({
|
||||
getAccessToken,
|
||||
handleUnauthorized,
|
||||
})
|
||||
})
|
||||
|
||||
describe('request', () => {
|
||||
it('makes GET request and returns data', async () => {
|
||||
const result = await request<{ status: string; timestamp: string; uptime: number }>(
|
||||
'/api/v1/health',
|
||||
)
|
||||
expect(result.data.status).toBe('ok')
|
||||
})
|
||||
|
||||
it('supports the single API envelope shape documented by Swagger', async () => {
|
||||
server.use(
|
||||
http.get(`${API}/test-single-envelope`, () =>
|
||||
HttpResponse.json({
|
||||
data: { ok: true },
|
||||
meta: { fromCache: true, cachedAt: '2026-06-17T00:00:00.000Z' },
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
const result = await request<{ ok: boolean }>('/api/v1/test-single-envelope')
|
||||
|
||||
expect(result).toEqual({
|
||||
data: { ok: true },
|
||||
meta: { fromCache: true, cachedAt: '2026-06-17T00:00:00.000Z' },
|
||||
})
|
||||
})
|
||||
|
||||
it('includes Authorization header when token is set', async () => {
|
||||
setAccessToken('test-token')
|
||||
let capturedAuth: string | null = null
|
||||
server.use(
|
||||
http.get(`${API}/test-auth`, ({ request }) => {
|
||||
capturedAuth = request.headers.get('Authorization')
|
||||
return HttpResponse.json({
|
||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
||||
})
|
||||
}),
|
||||
)
|
||||
await request('/api/v1/test-auth')
|
||||
expect(capturedAuth).toBe('Bearer test-token')
|
||||
})
|
||||
|
||||
it('retries on 401 and succeeds after refresh', async () => {
|
||||
setAccessToken('expired-token')
|
||||
let attempts = 0
|
||||
server.use(
|
||||
http.get(`${API}/test-retry`, ({ request }) => {
|
||||
attempts++
|
||||
const auth = request.headers.get('Authorization')
|
||||
if (auth === 'Bearer expired-token') {
|
||||
return new HttpResponse(null, { status: 401 })
|
||||
}
|
||||
return HttpResponse.json({
|
||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
||||
})
|
||||
}),
|
||||
http.post(`${API}/auth/refresh`, () =>
|
||||
HttpResponse.json({
|
||||
data: {
|
||||
data: {
|
||||
user: { id: 1, email: 'user@test.com', name: null, role: 'user' },
|
||||
accessToken: 'new-token',
|
||||
},
|
||||
meta: { fromCache: false, cachedAt: null },
|
||||
},
|
||||
}),
|
||||
),
|
||||
)
|
||||
const result = await request<{ ok: boolean }>('/api/v1/test-retry')
|
||||
expect(attempts).toBe(2)
|
||||
expect(result.data).toEqual({ ok: true })
|
||||
expect(getAccessToken()).toBe('new-token')
|
||||
})
|
||||
|
||||
it('throws on persistent 401 and clears token', async () => {
|
||||
setAccessToken('expired-token')
|
||||
let unauthorizedCalled = false
|
||||
setOnUnauthorized(() => {
|
||||
unauthorizedCalled = true
|
||||
})
|
||||
server.use(
|
||||
http.get(`${API}/test-fail`, () => new HttpResponse(null, { status: 401 })),
|
||||
http.post(`${API}/auth/refresh`, () => new HttpResponse(null, { status: 401 })),
|
||||
)
|
||||
await expect(request('/api/v1/test-fail')).rejects.toThrow('Сессия истекла')
|
||||
expect(getAccessToken()).toBeNull()
|
||||
expect(unauthorizedCalled).toBe(true)
|
||||
})
|
||||
|
||||
it('throws on non-ok response with status text', async () => {
|
||||
server.use(
|
||||
http.get(
|
||||
`${API}/test-error`,
|
||||
() => new HttpResponse('Not found', { status: 404, statusText: 'Not Found' }),
|
||||
),
|
||||
)
|
||||
await expect(request('/api/v1/test-error')).rejects.toThrow('Ошибка API: 404')
|
||||
})
|
||||
|
||||
it('sends JSON body for POST requests', async () => {
|
||||
let capturedBody: string | null = null
|
||||
server.use(
|
||||
http.post(`${API}/test-post`, async ({ request }) => {
|
||||
capturedBody = await request.text()
|
||||
return HttpResponse.json({
|
||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
||||
})
|
||||
}),
|
||||
)
|
||||
await request('/api/v1/test-post', undefined, { method: 'POST', body: { foo: 'bar' } })
|
||||
expect(capturedBody).toBe(JSON.stringify({ foo: 'bar' }))
|
||||
})
|
||||
|
||||
it('does not send auth header when skipAuth is true', async () => {
|
||||
setAccessToken('test-token')
|
||||
let capturedAuth: string | null = null
|
||||
server.use(
|
||||
http.get(`${API}/test-skip`, ({ request }) => {
|
||||
capturedAuth = request.headers.get('Authorization')
|
||||
return HttpResponse.json({
|
||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
||||
})
|
||||
}),
|
||||
)
|
||||
await request('/api/v1/test-skip', undefined, { skipAuth: true })
|
||||
expect(capturedAuth).toBeNull()
|
||||
})
|
||||
|
||||
it('sets query params correctly', async () => {
|
||||
let capturedUrl = ''
|
||||
server.use(
|
||||
http.get(`${API}/test-params`, ({ request }) => {
|
||||
capturedUrl = request.url
|
||||
return HttpResponse.json({
|
||||
data: { data: { ok: true }, meta: { fromCache: false, cachedAt: null } },
|
||||
})
|
||||
}),
|
||||
)
|
||||
await request('/api/v1/test-params', { q: 'sber', type: 'share' })
|
||||
expect(capturedUrl).toContain('q=sber')
|
||||
expect(capturedUrl).toContain('type=share')
|
||||
})
|
||||
})
|
||||
@ -1,36 +1,62 @@
|
||||
import type { components } from './types'
|
||||
|
||||
export { configureKyAuth, getHealth, request } from './kyClient'
|
||||
export type {
|
||||
AnalyticsResponse,
|
||||
ApiEnvelope,
|
||||
ApiResponseMeta,
|
||||
AuthResponse,
|
||||
BondHistoryItem,
|
||||
BondMarketData,
|
||||
BondResponse,
|
||||
BrokerAccount,
|
||||
BrokerEventItem,
|
||||
BrokerEventsData,
|
||||
BrokerEventsSummary,
|
||||
BrokerMoney,
|
||||
BrokerOperation,
|
||||
BrokerOperationCategory,
|
||||
BrokerOperationsPage,
|
||||
BrokerPortfolio,
|
||||
BrokerPosition,
|
||||
BrokerPositionsPage,
|
||||
CandleItem,
|
||||
DividendItem,
|
||||
HealthResponse,
|
||||
Portfolio,
|
||||
PortfolioDetail,
|
||||
PortfolioSummary,
|
||||
Position,
|
||||
PositionWithPrice,
|
||||
ScreenerItem,
|
||||
ScreenerResult,
|
||||
SearchResultItem,
|
||||
ShareHistoryItem,
|
||||
ShareResponse,
|
||||
StockMarketData,
|
||||
UserResponse,
|
||||
} from './responses'
|
||||
|
||||
export type ApiResponseMeta = components['schemas']['ApiResponseMeta']
|
||||
|
||||
export interface ApiEnvelope<T> {
|
||||
data: T
|
||||
meta: ApiResponseMeta
|
||||
}
|
||||
|
||||
// Auth
|
||||
export type AuthResponse = components['schemas']['AuthTokenDataDto']
|
||||
export type UserResponse = components['schemas']['AuthUserDto']
|
||||
|
||||
// Shares
|
||||
export type ShareResponse = components['schemas']['ShareResponseDto']
|
||||
export type StockMarketData = components['schemas']['StockMarketDataDto']
|
||||
export type DividendItem = components['schemas']['DividendItemDto']
|
||||
export type ShareHistoryItem = components['schemas']['HistoryItemDto']
|
||||
|
||||
// Bonds
|
||||
export type BondResponse = components['schemas']['BondResponseDto']
|
||||
export type BondMarketData = components['schemas']['BondMarketDataDto']
|
||||
export type BondHistoryItem = components['schemas']['BondHistoryItemDto']
|
||||
|
||||
// Candles
|
||||
export type CandleItem = components['schemas']['CandleItemDto']
|
||||
|
||||
// Search
|
||||
export type SearchResultItem = components['schemas']['SearchResultItemDto']
|
||||
|
||||
// Screener
|
||||
export type ScreenerResult = components['schemas']['ScreenerResultDto']
|
||||
export type ScreenerItem = components['schemas']['ScreenerItemDto']
|
||||
|
||||
// Portfolio
|
||||
export type Portfolio = components['schemas']['PortfolioListResponseDto']
|
||||
export type PortfolioDetail = components['schemas']['PortfolioDetailResponseDto']
|
||||
export type Position = components['schemas']['PositionResponseDto']
|
||||
export type PositionWithPrice = components['schemas']['PositionWithPriceDto']
|
||||
export type PortfolioSummary = components['schemas']['PortfolioSummaryDto']
|
||||
export type AnalyticsResponse = components['schemas']['AnalyticsResponseDto']
|
||||
|
||||
// Health
|
||||
export type HealthResponse = components['schemas']['HealthResponseDto']
|
||||
|
||||
// Broker
|
||||
export type BrokerAccount = components['schemas']['BrokerAccountResponseDto']
|
||||
export type BrokerPortfolio = components['schemas']['BrokerPortfolioResponseDto']
|
||||
export type BrokerMoney = components['schemas']['BrokerMoneyDto']
|
||||
export type BrokerPosition = components['schemas']['BrokerPositionResponseDto']
|
||||
export type BrokerOperation = components['schemas']['BrokerOperationResponseDto']
|
||||
export type BrokerOperationsPage = components['schemas']['BrokerOperationsPageResponseDto']
|
||||
export type BrokerPositionsPage = components['schemas']['BrokerPositionsPageResponseDto']
|
||||
export type BrokerOperationCategory =
|
||||
components['schemas']['BrokerOperationResponseDto']['category']
|
||||
|
||||
// Broker events
|
||||
export type BrokerEventItem = components['schemas']['BrokerEventItemDto']
|
||||
export type BrokerEventsData = components['schemas']['BrokerEventsDataDto']
|
||||
export type BrokerEventsSummary = components['schemas']['BrokerEventsSummaryDto']
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
import type { components } from './types'
|
||||
|
||||
// Re-exported from openapi-typescript codegen with friendly names
|
||||
|
||||
export type ApiResponseMeta = components['schemas']['ApiResponseMeta']
|
||||
|
||||
export interface ApiEnvelope<T> {
|
||||
data: T
|
||||
meta: ApiResponseMeta
|
||||
}
|
||||
|
||||
// Auth
|
||||
export type AuthResponse = components['schemas']['AuthTokenDataDto']
|
||||
export type UserResponse = components['schemas']['AuthUserDto']
|
||||
|
||||
// Shares
|
||||
export type ShareResponse = components['schemas']['ShareResponseDto']
|
||||
export type StockMarketData = components['schemas']['StockMarketDataDto']
|
||||
export type DividendItem = components['schemas']['DividendItemDto']
|
||||
export type ShareHistoryItem = components['schemas']['HistoryItemDto']
|
||||
|
||||
// Bonds
|
||||
export type BondResponse = components['schemas']['BondResponseDto']
|
||||
export type BondMarketData = components['schemas']['BondMarketDataDto']
|
||||
export type BondHistoryItem = components['schemas']['BondHistoryItemDto']
|
||||
|
||||
// Candles
|
||||
export type CandleItem = components['schemas']['CandleItemDto']
|
||||
|
||||
// Search
|
||||
export type SearchResultItem = components['schemas']['SearchResultItemDto']
|
||||
|
||||
// Screener
|
||||
export type ScreenerResult = components['schemas']['ScreenerResultDto']
|
||||
export type ScreenerItem = components['schemas']['ScreenerItemDto']
|
||||
|
||||
// Portfolio
|
||||
export type Portfolio = components['schemas']['PortfolioListResponseDto']
|
||||
export type PortfolioDetail = components['schemas']['PortfolioDetailResponseDto']
|
||||
export type Position = components['schemas']['PositionResponseDto']
|
||||
export type PositionWithPrice = components['schemas']['PositionWithPriceDto']
|
||||
export type PortfolioSummary = components['schemas']['PortfolioSummaryDto']
|
||||
export type AnalyticsResponse = components['schemas']['AnalyticsResponseDto']
|
||||
|
||||
// Health
|
||||
export type HealthResponse = components['schemas']['HealthResponseDto']
|
||||
|
||||
// Broker — some nullable fields still have Record<string, never> from backend
|
||||
export type BrokerAccount = components['schemas']['BrokerAccountResponseDto']
|
||||
export type BrokerPortfolio = components['schemas']['BrokerPortfolioResponseDto']
|
||||
export type BrokerMoney = components['schemas']['BrokerMoneyDto']
|
||||
export type BrokerPosition = components['schemas']['BrokerPositionResponseDto']
|
||||
export type BrokerOperation = components['schemas']['BrokerOperationResponseDto']
|
||||
export type BrokerOperationsPage = components['schemas']['BrokerOperationsPageResponseDto']
|
||||
export type BrokerPositionsPage = components['schemas']['BrokerPositionsPageResponseDto']
|
||||
export type BrokerOperationCategory =
|
||||
components['schemas']['BrokerOperationResponseDto']['category']
|
||||
|
||||
// Broker events
|
||||
export type BrokerEventItem = components['schemas']['BrokerEventItemDto']
|
||||
export type BrokerEventsData = components['schemas']['BrokerEventsDataDto']
|
||||
export type BrokerEventsSummary = components['schemas']['BrokerEventsSummaryDto']
|
||||
@ -1,4 +1,4 @@
|
||||
import type { BrokerMoney } from '@/shared/api/responses'
|
||||
import type { BrokerMoney } from '@/shared/api'
|
||||
|
||||
export function formatBrokerCurrencyValue(currency: string, value: number): string {
|
||||
return new Intl.NumberFormat('ru-RU', {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { createContext } from 'react'
|
||||
import type { UserResponse } from '@/shared/api/responses'
|
||||
import type { UserResponse } from '@/shared/api'
|
||||
|
||||
export interface SessionContextValue {
|
||||
user: UserResponse | null
|
||||
|
||||
@ -8,7 +8,7 @@ import type {
|
||||
ShareResponse,
|
||||
StockMarketData,
|
||||
UserResponse,
|
||||
} from '@/shared/api/responses'
|
||||
} from '@/shared/api'
|
||||
|
||||
export function createMockMarketData(overrides: Partial<StockMarketData> = {}): StockMarketData {
|
||||
return {
|
||||
|
||||
@ -1,10 +1,5 @@
|
||||
import { HttpResponse, http } from 'msw'
|
||||
import type {
|
||||
BondResponse,
|
||||
CandleItem,
|
||||
SearchResultItem,
|
||||
ShareResponse,
|
||||
} from '@/shared/api/responses'
|
||||
import type { BondResponse, CandleItem, SearchResultItem, ShareResponse } from '@/shared/api'
|
||||
|
||||
const API = '/api/v1'
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { BondResponse } from '@/shared/api/responses'
|
||||
import type { BondResponse } from '@/shared/api'
|
||||
|
||||
interface BondDetailsProps {
|
||||
bond: BondResponse
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
||||
import type { PositionWithPrice } from '@/shared/api'
|
||||
|
||||
interface Props {
|
||||
position: PositionWithPrice
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
||||
import type { PositionWithPrice } from '@/shared/api'
|
||||
import { BondPositionRow } from './BondPositionRow'
|
||||
|
||||
interface Props {
|
||||
|
||||
@ -2,7 +2,7 @@ import { Alert, Button, Heading, Skeleton, Text } from '@moex-vibe/design-system
|
||||
import { Box } from '@mui/material'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { buildBrokerAllocation } from '@/entities/broker-position'
|
||||
import type { BrokerAccount, BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerAccount, BrokerPortfolio } from '@/shared/api'
|
||||
import {
|
||||
formatBrokerDate,
|
||||
formatBrokerMoney,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Text } from '@moex-vibe/design-system'
|
||||
import { Box } from '@mui/material'
|
||||
import { buildBrokerAllocation } from '@/entities/broker-position'
|
||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerPortfolio } from '@/shared/api'
|
||||
import { formatBrokerCurrencyValue } from '@/shared/lib/formatters'
|
||||
|
||||
const RADIUS = 44
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
getBrokerOperationTypeLabel,
|
||||
} from '@/entities/broker-operation'
|
||||
import { getBrokerInstrumentPath } from '@/entities/broker-position'
|
||||
import type { BrokerOperation, BrokerOperationsPage } from '@/shared/api/responses'
|
||||
import type { BrokerOperation, BrokerOperationsPage } from '@/shared/api'
|
||||
import { formatBrokerSignedMoney } from '@/shared/lib/formatters'
|
||||
import { TableSkeleton } from '@/shared/ui/TableSkeleton'
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Text } from '@moex-vibe/design-system'
|
||||
import { Box } from '@mui/material'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerMoney, BrokerPortfolio } from '@/shared/api'
|
||||
import { formatBrokerMoney, pluralize } from '@/shared/lib/formatters'
|
||||
|
||||
function allocationPercent(value: BrokerMoney | null, total: BrokerMoney | null) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Text } from '@moex-vibe/design-system'
|
||||
import { Box } from '@mui/material'
|
||||
import type { BrokerPortfolio } from '@/shared/api/responses'
|
||||
import type { BrokerPortfolio } from '@/shared/api'
|
||||
import {
|
||||
formatBrokerMoney as formatMoney,
|
||||
formatBrokerPercent as formatPercent,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Button, Heading, Skeleton, Text } from '@moex-vibe/design-system'
|
||||
import { Box } from '@mui/material'
|
||||
import type { BrokerPositionsPage as BrokerPositionsPageData } from '@/shared/api/responses'
|
||||
import type { BrokerPositionsPage as BrokerPositionsPageData } from '@/shared/api'
|
||||
import { formatBrokerMoney as formatMoney } from '@/shared/lib/formatters'
|
||||
import { PositionTicker } from './PositionTicker'
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Box } from '@mui/material'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { getBrokerInstrumentPath } from '@/entities/broker-position'
|
||||
import type { BrokerPosition } from '@/shared/api/responses'
|
||||
import type { BrokerPosition } from '@/shared/api'
|
||||
|
||||
export function PositionTicker({ position }: { position: BrokerPosition }) {
|
||||
const label = position.ticker || position.figi || '-'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { DividendItem } from '@/shared/api/responses'
|
||||
import type { DividendItem } from '@/shared/api'
|
||||
|
||||
interface DividendsTableProps {
|
||||
dividends: DividendItem[]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { PortfolioSummary } from '@/shared/api/responses'
|
||||
import type { PortfolioSummary } from '@/shared/api'
|
||||
|
||||
export function AnalyticsSummary({ summary }: { summary: PortfolioSummary }) {
|
||||
const formatRub = (val: number | null) =>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import type { Portfolio } from '@/shared/api/responses'
|
||||
import type { Portfolio } from '@/shared/api'
|
||||
import { pluralize } from '@/shared/lib/formatters'
|
||||
|
||||
export function PortfolioCard({ portfolio }: { portfolio: Portfolio }) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useState } from 'react'
|
||||
import type { Portfolio } from '@/shared/api/responses'
|
||||
import type { Portfolio } from '@/shared/api'
|
||||
|
||||
interface Props {
|
||||
initial?: Portfolio
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
||||
import type { PositionWithPrice } from '@/shared/api'
|
||||
|
||||
interface AllocationChartProps {
|
||||
positions: PositionWithPrice[]
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { PortfolioDetail } from '@/shared/api/responses'
|
||||
import type { PortfolioDetail } from '@/shared/api'
|
||||
import { AllocationChart } from './AllocationChart'
|
||||
|
||||
export function PortfolioSummary({ portfolio }: { portfolio: PortfolioDetail }) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { useState } from 'react'
|
||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
||||
import type { PositionWithPrice } from '@/shared/api'
|
||||
|
||||
interface Props {
|
||||
position: PositionWithPrice
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { PositionWithPrice } from '@/shared/api/responses'
|
||||
import type { PositionWithPrice } from '@/shared/api'
|
||||
import { SharePositionRow } from './SharePositionRow'
|
||||
|
||||
interface Props {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ShareResponse } from '@/shared/api/responses'
|
||||
import type { ShareResponse } from '@/shared/api'
|
||||
|
||||
interface StockDetailsProps {
|
||||
stock: ShareResponse
|
||||
|
||||
@ -47,10 +47,14 @@
|
||||
### Phase 3 — Unify API Types
|
||||
*Низкий риск*
|
||||
|
||||
1. Убедиться, что все entity API импортируют из `types.ts` (codegen)
|
||||
> **Фактический подход: ре-экспорты в index.ts вместо types.ts.**
|
||||
> В процессе реализации выяснилось, что direct-импорты из `types.ts` ведут к многословным `components['schemas']['XxxDto']` в 60+ файлах. Принято решение перенести friendly-name ре-экспорты из `responses.ts` в `index.ts`. Таким образом `responses.ts` удалён, а единая точка входа — `@/shared/api`.
|
||||
|
||||
1. Перенести friendly-name type-алиасы из `responses.ts` в `shared/api/index.ts`
|
||||
2. Удалить `shared/api/responses.ts`
|
||||
3. Перенести normalizeEnvelope (ky-версию) в `shared/api/kyClient.ts`
|
||||
4. Прогнать `npm run build`
|
||||
3. Переключить все импорты с `@/shared/api/responses` на `@/shared/api`
|
||||
4. Перенести normalizeEnvelope (ky-версию) в `shared/api/kyClient.ts`
|
||||
5. Прогнать `npm run build`
|
||||
|
||||
### Phase 4 — MSW Browser
|
||||
*Низкий риск, handlers готовы*
|
||||
|
||||
@ -21,9 +21,9 @@
|
||||
|
||||
## Phase 2: Biome Migration
|
||||
|
||||
- [ ] Research: проверить Biome plugin system на поддержку FSD/import-no-restricted-paths
|
||||
- [x] Research: проверить Biome plugin system на поддержку FSD/import-no-restricted-paths — не поддерживается, ESLint оставлен для FSD
|
||||
- [x] Установить `@biomejs/biome` (devDependency)
|
||||
- [ ] Запустить `npx @biomejs/biome migrate eslint --write`
|
||||
- [x] Запустить `npx @biomejs/biome migrate eslint --write` — 0 правил мигрировано, FSD-правила несовместимы, конфиг откачен
|
||||
- [x] Создать и настроить `biome.json` под проект
|
||||
- [x] ESLint оставлен только для FSD-правил (`.eslintrc.cjs`)
|
||||
- [x] Удалить зависимость prettier (ESLint core, @typescript-eslint/*, eslint-plugin-* оставлены для FSD-правил)
|
||||
@ -37,11 +37,11 @@
|
||||
|
||||
## Phase 3: Unify API Types
|
||||
|
||||
- [ ] Аудит импортов: entity API используют `types.ts` (codegen) или `responses.ts`?
|
||||
- [ ] Переключить все импорты с `responses.ts` на `types.ts` (если типы есть в codegen)
|
||||
- [ ] Удалить `shared/api/responses.ts` (после переключения)
|
||||
- [x] Аудит импортов: entity API используют `types.ts` (codegen) или `responses.ts`?
|
||||
- [x] Переключить все импорты с `responses.ts` на `@/shared/api` (типы перенесены в `index.ts` вместо рукописного файла)
|
||||
- [x] Удалить `shared/api/responses.ts` (после переключения)
|
||||
- [x] normalizeEnvelope перенесён в `shared/api/kyClient.ts`
|
||||
- [ ] `npm run build` — сборка проходит
|
||||
- [x] `npm run build` — сборка проходит
|
||||
|
||||
## Phase 4: MSW Browser
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user