docs: refresh project documentation
Some checks failed
CI / lint (pull_request) Successful in 2m14s
CI / test (pull_request) Successful in 1m52s
CI / build (pull_request) Failing after 2m15s
CI / test (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
Sergey Krylov 2026-06-15 20:02:50 +03:00
parent c062850e83
commit 117f182851
12 changed files with 215 additions and 32 deletions

View File

@ -2,7 +2,7 @@
## Репозиторий
npm workspaces монорепозиторий: `apps/backend` (NestJS), `apps/frontend` (React + Vite).
npm workspaces монорепозиторий: `apps/backend` (NestJS), `apps/frontend` (React + Vite), `apps/docs` (Docusaurus).
## Обязательный подход к разработке
@ -16,13 +16,18 @@ npm workspaces монорепозиторий: `apps/backend` (NestJS), `apps/fr
|---|---|
| `npm run dev:backend` | Запуск NestJS в режиме watch на :3000 |
| `npm run dev:frontend` | Vite dev-сервер на :5173, проксирует `/api` → :3000 |
| `npm run dev:docs` | Docusaurus dev-сервер |
| `npm run build:backend` | `nest build` |
| `npm run build:frontend` | `tsc -b && vite build` (в две фазы) |
| `npm run build:docs` | `docusaurus build` |
| `npm run test:backend` | `vitest run` (SWC, не ts-jest) |
| `npm run lint` | ESLint только для бэкенда |
| `npm run test:frontend` | Frontend Vitest suite |
| `npm run lint` | ESLint для backend и frontend |
| `npm run format` | Prettier для всех `*.{ts,tsx}` |
| `npm run codegen -w apps/frontend` | `openapi-typescript` из локального Swagger → `src/api/types.ts` |
Live MOEX integration tests opt-in: `npm run test:integration -w apps/backend`.
Один тест: `npx vitest run path/to/test.spec.ts -w apps/backend`
## Переменные окружения
@ -46,7 +51,7 @@ npm workspaces монорепозиторий: `apps/backend` (NestJS), `apps/fr
## Архитектура
- **Бэкенд** — единственный клиент MOEX. Фронтенд никогда не обращается к MOEX напрямую.
- Feature-модули: `PrismaModule` (глобальный), `MoexClientModule` (глобальный), `CacheModule` (глобальный), `AuthModule`, `SharesModule`, `BondsModule`, `SecuritiesModule`, `CandlesModule`, `HealthModule`.
- Feature-модули: `PrismaModule` (глобальный), `MoexClientModule` (глобальный), `CacheModule` (глобальный), `AuthModule`, `SharesModule`, `BondsModule`, `SecuritiesModule`, `CandlesModule`, `PortfolioModule`, `HealthModule`.
- `MoexClientService` использует p-queue (rate limiter) + circuit breaker (5 ошибок → 30s открыт).
- In-memory кеш через `@nestjs/cache-manager`. Путь миграции на Redis описан (см. ADR-002).
- Аутентификация: JWT access token (15m, в памяти) + refresh token (7d, httpOnly cookie, bcrypt hash в БД). Глобальный `JwtAuthGuard` (`@Public()` для открытых эндпоинтов).
@ -70,5 +75,6 @@ npm workspaces монорепозиторий: `apps/backend` (NestJS), `apps/fr
- Prettier: одинарные кавычки, trailing commas, printWidth 100, точки с запятой.
- Бэкенд: `const`, PascalCase для модулей/контроллеров/сервисов, DTO в `dto/` внутри каждого модуля.
- Бэкенд использует SWC через `unplugin-swc` (vitest config).
- Тесты фронтенда отсутствуют.
- CI/CD в репозитории нет.
- Тесты фронтенда есть: Vitest + Testing Library + MSW.
- CI находится в `.gitea/workflows/ci.yml`.
- Pre-commit checks настроены через Husky и lint-staged.

View File

@ -6,6 +6,7 @@
- **Backend:** NestJS, TypeScript, OpenAPI (Swagger)
- **Frontend:** React, TypeScript, Vite, TanStack Query, lightweight-charts
- **Docs:** Docusaurus
- **Infrastructure:** Docker, docker-compose
## Quick Start
@ -36,6 +37,13 @@ docker compose up --build
```bash
npm run test:backend
npm run test:frontend
```
Live MOEX integration checks are opt-in:
```bash
npm run test:integration -w apps/backend
```
## Project Structure
@ -44,6 +52,7 @@ npm run test:backend
apps/
backend/ — NestJS API (single point of access to MOEX ISS)
frontend/ — React SPA with Vite
docs/ — Docusaurus documentation site
docs/
architecture/ — ADR documents and diagrams
openapi/ — OpenAPI specification

View File

@ -130,6 +130,25 @@
}
```
### `GET /securities/screener`
Скринер ценных бумаг по параметрам цены, объёма, доходности, дюрации, купона и срока погашения.
**Parameters:**
| Param | Type | Required | Description |
|---|---|---|---|
| `type` | enum | yes | `share` или `bond` |
| `priceMin`, `priceMax` | number | no | Диапазон цены |
| `volumeMin` | number | no | Минимальный объём |
| `yieldMin`, `yieldMax` | number | no | Диапазон доходности облигаций |
| `durationMin`, `durationMax` | number | no | Диапазон дюрации |
| `sortBy` | string | no | Поле сортировки |
| `sortOrder` | enum | no | `asc` или `desc` |
| `page`, `pageSize` | integer | no | Пагинация |
**Response:** `{ data: { items, total, page, pageSize, totalPages }, meta }`.
## Shares
### `GET /securities/shares/:secid`
@ -354,3 +373,22 @@
"meta": { "fromCache": false, "cachedAt": null }
}
```
## Portfolios
Все portfolio endpoints защищены JWT и возвращают envelope `{ data, meta }`.
| Endpoint | Method | Description |
|---|---|---|
| `/api/v1/portfolios` | GET | Список портфелей пользователя |
| `/api/v1/portfolios` | POST | Создать портфель |
| `/api/v1/portfolios/:id` | GET | Детали портфеля с позициями и текущими ценами |
| `/api/v1/portfolios/:id` | PATCH | Обновить портфель |
| `/api/v1/portfolios/:id` | DELETE | Удалить портфель |
| `/api/v1/portfolios/:id/positions` | POST | Добавить позицию |
| `/api/v1/portfolios/:id/positions/:positionId` | PATCH | Обновить позицию |
| `/api/v1/portfolios/:id/positions/:positionId` | DELETE | Удалить позицию |
| `/api/v1/portfolios/:id/analytics` | GET | Аналитика портфеля и PnL |
`DELETE` endpoints возвращают `{ data: null, meta }`, что отражено в OpenAPI schema и frontend
codegen types.

View File

@ -1,6 +1,7 @@
# Portfolio Module
The Portfolio module allows users to create and manage virtual investment portfolios for tracking purposes.
Portfolio module позволяет пользователям создавать и вести виртуальные инвестиционные портфели для
аналитики и отслеживания позиций.
## Overview
@ -10,19 +11,19 @@ The Portfolio module allows users to create and manage virtual investment portfo
## API Endpoints
All endpoints require JWT authentication (`JwtAuthGuard`).
Все endpoints требуют JWT authentication (`JwtAuthGuard`).
| Endpoint | Method | Description |
|---|---|---|
| `/api/v1/portfolios` | GET | List user's portfolios |
| `/api/v1/portfolios` | POST | Create portfolio |
| `/api/v1/portfolios/:id` | GET | Portfolio detail with enriched positions and analytics summary |
| `/api/v1/portfolios/:id/analytics` | GET | Detailed portfolio analytics with PnL |
| `/api/v1/portfolios/:id/patch` | PATCH | Update portfolio (name, description, currency) |
| `/api/v1/portfolios/:id` | DELETE | Delete portfolio (cascade deletes positions) |
| `/api/v1/portfolios/:id/positions` | POST | Add position (accepts buyPrice, buyDate) |
| `/api/v1/portfolios/:id/positions/:posId` | PATCH | Update position (quantity, buyPrice, buyDate, notes) |
| `/api/v1/portfolios/:id/positions/:posId` | DELETE | Remove position |
| `/api/v1/portfolios` | GET | Список портфелей пользователя |
| `/api/v1/portfolios` | POST | Создать портфель |
| `/api/v1/portfolios/:id` | GET | Детали портфеля с обогащёнными позициями и analytics summary |
| `/api/v1/portfolios/:id/analytics` | GET | Детальная аналитика портфеля и PnL |
| `/api/v1/portfolios/:id` | PATCH | Обновить портфель (name, description, currency) |
| `/api/v1/portfolios/:id` | DELETE | Удалить портфель вместе с позициями |
| `/api/v1/portfolios/:id/positions` | POST | Добавить позицию (buyPrice, buyDate, notes, tags) |
| `/api/v1/portfolios/:id/positions/:positionId` | PATCH | Обновить позицию |
| `/api/v1/portfolios/:id/positions/:positionId` | DELETE | Удалить позицию |
## Domain Model

View File

@ -24,6 +24,19 @@ openapi-typescript http://localhost:3000/api/docs-json -o src/api/types.ts
### Output
- `apps/frontend/src/api/types.ts` — сгенерированные типы `paths` и `operations`
- `docs/openapi/openapi.yaml` — статический snapshot Swagger JSON для ревью и документации
### Verify Artifacts
После регенерации OpenAPI artifacts запустите:
```bash
npm run test -w apps/backend -- src/openapi-artifacts.spec.ts
```
Тест проверяет, что checked-in frontend types и YAML содержат актуальные auth, screener и portfolio
paths, не содержат локальный alternate port и сохраняют важные schema metadata для nullable полей,
array enum tags и typed response envelopes.
### Manual Types

View File

@ -5,13 +5,16 @@
| Command | Description |
|---|---|
| `npm run dev:backend` | Запуск NestJS в режиме watch на :3000 |
| `npm run dev:frontend` | Vite dev-сервер на :5173, проксирует `/api` → :3000 |
| `npm run dev:frontend` | Vite dev-сервер на `:5173`, проксирует `/api` на backend |
| `npm run dev:docs` | Docusaurus dev-сервер документации |
| `npm run build:backend` | `nest build` |
| `npm run build:frontend` | `tsc -b && vite build` (в две фазы) |
| `npm run test:backend` | `vitest run` (SWC, не ts-jest) |
| `npm run lint` | ESLint только для бэкенда |
| `npm run build:frontend` | `tsc -b && vite build` |
| `npm run build:docs` | `docusaurus build` |
| `npm run test:backend` | Offline backend unit tests через Vitest |
| `npm run test:frontend` | Frontend tests через Vitest + Testing Library |
| `npm run lint` | ESLint для backend и frontend |
| `npm run format` | Prettier для всех `*.{ts,tsx}` |
| `npm run format:check` | Prettier check для всех `*.{ts,tsx}` |
| `npm run format:check` | Проверка Prettier для всех `*.{ts,tsx}` |
## Backend Workspace
@ -23,6 +26,7 @@
| `npm run lint -w apps/backend` | ESLint для `{src,test}/**/*.ts` |
| `npm run test -w apps/backend` | `vitest run` |
| `npm run test:watch -w apps/backend` | `vitest` |
| `npm run test:integration -w apps/backend` | Opt-in live MOEX integration tests, требуется network access |
## Frontend Workspace
@ -32,6 +36,16 @@
| `npm run build -w apps/frontend` | `tsc -b && vite build` |
| `npm run preview -w apps/frontend` | `vite preview` |
| `npm run codegen -w apps/frontend` | `openapi-typescript` из Swagger → `src/api/types.ts` |
| `npm run lint -w apps/frontend` | ESLint для `src/**/*.{ts,tsx}` |
| `npm run test -w apps/frontend` | Frontend Vitest suite |
## Docs Workspace
| Command | Description |
|---|---|
| `npm run dev -w apps/docs` | Docusaurus dev-server |
| `npm run build -w apps/docs` | Production build документации |
| `npm run serve -w apps/docs` | Локальная проверка production build |
## Single Test

View File

@ -18,7 +18,7 @@ Prettier (`.prettierrc`):
## Linting
ESLint только для бэкенда (`apps/backend`).
ESLint запускается для backend (`apps/backend`) и frontend (`apps/frontend`).
Плагины:
- `@typescript-eslint/eslint-plugin`

View File

@ -39,4 +39,25 @@ npm run test:watch -w apps/backend
## Frontend Tests
Фронтенд-тесты отсутствуют.
Фреймворк: **Vitest 4** + **React Testing Library** + **MSW**.
Запуск:
```bash
npm run test:frontend
# или
npm run test -w apps/frontend
```
Тесты покрывают API-клиент, auth context, hooks, базовые pages и shared components.
## Live MOEX Integration Tests
Live MOEX checks вынесены из default backend suite.
```bash
npm run test:integration -w apps/backend
```
Эта команда opt-in: она требует network access и может падать при недоступности MOEX или сетевых
ограничениях окружения.

View File

@ -8,6 +8,7 @@
async function request<T>(
path: string,
params?: Record<string, string>,
options?: { method?: string; body?: unknown; skipAuth?: boolean },
): Promise<{ data: T; meta: ApiResponseMeta }>
```
@ -20,7 +21,14 @@ async function request<T>(
| Function | Method | Path |
|---|---|---|
| `getHealth()` | GET | `/api/v1/health` |
| `register(data)` | POST | `/api/v1/auth/register` |
| `login(data)` | POST | `/api/v1/auth/login` |
| `refresh()` | POST | `/api/v1/auth/refresh` |
| `logout()` | POST | `/api/v1/auth/logout` |
| `getProfile()` / `getMe()` | GET | `/api/v1/auth/me` |
| `updateProfile(data)` | PATCH | `/api/v1/auth/me` |
| `searchSecurities(q, type?, limit?)` | GET | `/api/v1/securities/search` |
| `screenSecurities(query)` / `getScreenerResults(query)` | GET | `/api/v1/securities/screener` |
| `getShare(secid)` | GET | `/api/v1/securities/shares/:secid` |
| `getShareMarketData(secid)` | GET | `/api/v1/securities/shares/:secid/marketdata` |
| `getShareDividends(secid)` | GET | `/api/v1/securities/shares/:secid/dividends` |
@ -30,6 +38,15 @@ async function request<T>(
| `getBondHistory(secid, from, till)` | GET | `/api/v1/securities/bonds/:secid/history` |
| `getShareCandles(secid, interval, from, till)` | GET | `/api/v1/securities/shares/:secid/candles` |
| `getBondCandles(secid, interval, from, till)` | GET | `/api/v1/securities/bonds/:secid/candles` |
| `getPortfolios()` | GET | `/api/v1/portfolios` |
| `createPortfolio(data)` | POST | `/api/v1/portfolios` |
| `getPortfolio(id)` | GET | `/api/v1/portfolios/:id` |
| `updatePortfolio(id, data)` | PATCH | `/api/v1/portfolios/:id` |
| `deletePortfolio(id)` | DELETE | `/api/v1/portfolios/:id` |
| `addPosition(portfolioId, data)` | POST | `/api/v1/portfolios/:id/positions` |
| `updatePosition(portfolioId, positionId, data)` | PATCH | `/api/v1/portfolios/:id/positions/:positionId` |
| `removePosition(portfolioId, positionId)` | DELETE | `/api/v1/portfolios/:id/positions/:positionId` |
| `getPortfolioAnalytics(portfolioId)` | GET | `/api/v1/portfolios/:id/analytics` |
## Types
@ -41,5 +58,8 @@ async function request<T>(
- `BondResponse` — спецификация облигации + `BondMarketData`
- `CandleItem`, `DividendItem`, `ShareHistoryItem`, `BondHistoryItem`
- `SearchResultItem`, `HealthResponse`
- `UserResponse`, `AuthResponse`
- `Portfolio`, `PortfolioDetail`, `Position`, `PortfolioSummary`, `AnalyticsResponse`
- `ScreenerItem`, `ScreenerResult`
Codegen-типы из OpenAPI в `api/types.ts` (генерируются через `npm run codegen`).

View File

@ -9,6 +9,7 @@ React SPA, собранная с Vite.
- TanStack Query v5
- lightweight-charts v4
- openapi-fetch (с рукописными типами `responses.ts`)
- Vitest + Testing Library + MSW
- Vite 5
## Source Layout
@ -19,10 +20,22 @@ apps/frontend/src/
├── App.tsx # BrowserRouter
├── routes.tsx # Маршруты
├── api/
│ ├── auth.ts # Auth API helpers
│ ├── client.ts # HTTP-клиент (fetch)
│ ├── portfolio.ts # Portfolio API helpers
│ ├── responses.ts # Типы ответов (ручные)
│ ├── screener.ts # Screener API helpers
│ └── types.ts # Типы из openapi-typescript
├── context/
│ └── AuthContext.tsx
├── hooks/
│ ├── useAuth.ts
│ ├── usePortfolio.ts
│ ├── usePortfolioAnalytics.ts
│ ├── usePortfolioMutations.ts
│ ├── usePortfolios.ts
│ ├── usePositionMutations.ts
│ ├── useScreener.ts
│ ├── useSearch.ts
│ ├── useStock.ts
│ ├── useStockCandles.ts
@ -31,14 +44,24 @@ apps/frontend/src/
│ └── useBondCandles.ts
├── components/
│ ├── Layout.tsx
│ ├── ProtectedRoute.tsx
│ ├── SearchBar.tsx
│ ├── PriceChart.tsx
│ ├── StockDetails.tsx
│ └── BondDetails.tsx
│ ├── BondDetails.tsx
│ └── portfolios/
├── pages/
│ ├── HomePage.tsx
│ ├── StockPage.tsx
│ └── BondPage.tsx
│ ├── BondPage.tsx
│ ├── LoginPage.tsx
│ ├── RegisterPage.tsx
│ ├── ProfilePage.tsx
│ ├── portfolios/
│ └── screener/
├── test/
│ ├── handlers.ts # MSW handlers
│ └── test-utils.tsx
└── styles.css
```

View File

@ -2,11 +2,17 @@
Определены в `apps/frontend/src/routes.tsx`.
| Path | Component | Description |
|---|---|---|
| `/` | `HomePage` | Главная страница с приветствием |
| `/stocks/:secid` | `StockPage` | Страница акции |
| `/bonds/:secid` | `BondPage` | Страница облигации |
| Path | Component | Access | Description |
|---|---|---|---|
| `/` | `HomePage` | Public | Главная страница |
| `/stocks/:secid` | `StockPage` | Public | Страница акции |
| `/bonds/:secid` | `BondPage` | Public | Страница облигации |
| `/screener` | `ScreenerPage` | Public | Скринер ценных бумаг |
| `/login` | `LoginPage` | Public | Вход |
| `/register` | `RegisterPage` | Public | Регистрация |
| `/profile` | `ProfilePage` | Protected | Профиль текущего пользователя |
| `/portfolios` | `PortfoliosListPage` | Protected | Список портфелей |
| `/portfolios/:id` | `PortfolioDetailPage` | Protected | Детальная страница портфеля |
Все страницы обёрнуты в `Layout`, который содержит:
@ -22,6 +28,33 @@
<Route path="/" element={<HomePage />} />
<Route path="/stocks/:secid" element={<StockPage />} />
<Route path="/bonds/:secid" element={<BondPage />} />
<Route path="/screener" element={<ScreenerPage />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route
path="/profile"
element={
<ProtectedRoute>
<ProfilePage />
</ProtectedRoute>
}
/>
<Route
path="/portfolios"
element={
<ProtectedRoute>
<PortfoliosListPage />
</ProtectedRoute>
}
/>
<Route
path="/portfolios/:id"
element={
<ProtectedRoute>
<PortfolioDetailPage />
</ProtectedRoute>
}
/>
</Route>
</Routes>
```

View File

@ -1,3 +1,7 @@
---
slug: /
---
# MoexVibe
Веб-приложение для анализа ценных бумаг Московской биржи (MOEX).
@ -15,7 +19,8 @@
moex-vibe/
├── apps/
│ ├── backend/ # NestJS API (единственная точка доступа к MOEX)
│ └── frontend/ # React SPA
│ ├── frontend/ # React SPA
│ └── docs/ # Docusaurus documentation site
├── docs/
│ ├── architecture/ # ADR и диаграммы
│ ├── openapi/ # OpenAPI-спецификация
@ -31,6 +36,6 @@ moex-vibe/
## Key Principles
- Backend — единственный клиент MOEX. Frontend никогда не обращается к MOEX напрямую.
- npm workspaces монорепозиторий: `apps/backend` и `apps/frontend`.
- npm workspaces монорепозиторий: `apps/backend`, `apps/frontend` и `apps/docs`.
- Глобальный префикс API: `/api/v1`. Swagger: `/api/docs`.
- Ответы API обёрнуты в `{ data: T, meta: { fromCache, cachedAt } }`.