From af36d2e7cf0349726e40ed197999f239317603e3 Mon Sep 17 00:00:00 2001 From: Sergey Krylov Date: Sat, 20 Jun 2026 14:28:10 +0300 Subject: [PATCH] docs: define frontend fsd app+auth spec, plan, tasks --- docs/features/frontend-fsd-app-auth/plan.md | 178 +++++++++++++++++++ docs/features/frontend-fsd-app-auth/spec.md | 92 ++++++++++ docs/features/frontend-fsd-app-auth/tasks.md | 57 ++++++ 3 files changed, 327 insertions(+) create mode 100644 docs/features/frontend-fsd-app-auth/plan.md create mode 100644 docs/features/frontend-fsd-app-auth/spec.md create mode 100644 docs/features/frontend-fsd-app-auth/tasks.md diff --git a/docs/features/frontend-fsd-app-auth/plan.md b/docs/features/frontend-fsd-app-auth/plan.md new file mode 100644 index 0000000..25cbaf9 --- /dev/null +++ b/docs/features/frontend-fsd-app-auth/plan.md @@ -0,0 +1,178 @@ +# Frontend FSD App + Auth — План реализации + +## Стратегия миграции + +Поэтапный рефакторинг без big-bang. Каждый legacy-файл сначала превращается в re-export shim +после переноса кода. Все изменения — только перегруппировка, поведение не меняется. + +## Целевая структура + +``` +src/ +├── app/ +│ ├── App.tsx # BrowserRouter + AppRoutes +│ ├── index.ts # barrel (export { default as App } from './App') +│ ├── providers/ +│ │ ├── AppProviders.tsx # QueryClientProvider + SessionProvider +│ │ ├── SessionProvider.tsx # Auth orchestration (из context/AuthContext) +│ │ └── index.ts # barrel +│ ├── routing/ +│ │ ├── AppRoutes.tsx # Route definitions (из routes.tsx) +│ │ ├── ProtectedRoute.tsx # Auth guard (из components/ProtectedRoute) +│ │ └── index.ts # barrel +│ └── layouts/ +│ ├── AppLayout.tsx # Header + nav + Outlet (из components/Layout) +│ └── index.ts # barrel +│ +├── entities/ +│ └── session/ +│ ├── index.ts # barrel +│ ├── api/ +│ │ ├── sessionApi.ts # login, register, refresh, logout, getMe, updateProfile +│ │ └── sessionApi.test.ts # перенос из api/auth.test.ts +│ └── model/ +│ ├── sessionContext.ts # SessionContext + SessionContextValue +│ ├── useSession.ts # хук (из hooks/useAuth.ts) +│ └── useSession.test.tsx # перенос из hooks/useAuth.test.tsx +│ +├── pages/ +│ ├── LoginPage.tsx # обновлён импорт на @/entities/session +│ ├── RegisterPage.tsx # обновлён импорт +│ └── ProfilePage.tsx # обновлён импорт +│ +├── api/ +│ ├── auth.ts → SHIM для entities/session/api/sessionApi +│ ├── client.ts → SHIM (уже есть) +│ ├── responses.ts → SHIM (уже есть) +│ └── ... (остальные файлы без изменений) +│ +├── hooks/ +│ ├── useAuth.ts → SHIM для entities/session/model/useSession +│ └── ... (остальные shim'ы уже есть) +│ +├── context/ +│ └── AuthContext.tsx → SHIM (реэкспорт SessionProvider + sessionContext) +│ +├── components/ +│ ├── Layout.tsx → SHIM для app/layouts/AppLayout +│ ├── ProtectedRoute.tsx → SHIM для app/routing/ProtectedRoute +│ └── ... (остальные без изменений) +│ +└── main.tsx → обновлён: использует AppProviders +``` + +## Потоки данных + +### Auth flow (без изменений в runtime) + +``` +User action → Page (LoginPage) + → useSession().login(email, password) + → sessionApi.login(email, password) [entities/session/api] + → request() [shared/api/client] + → SessionProvider.updateSession(result) [app/providers] + → setUser + setAccessToken + → SessionContext.Provider value update +``` + +### Session restore на старте + +``` +main.tsx + → AppProviders + → SessionProvider (mount) + → sessionApi.refresh() + → if ok: updateSession(result) + → if fail: keep anonymous + → App → AppRoutes → ProtectedRoute → page +``` + +## Этапы реализации + +### Phase 1: entities/session/ + +1. Создать `entities/session/api/sessionApi.ts` — скопировать 6 функций из `api/auth.ts`, + заменить импорт `./client` на `@/shared/api/client`, импорт `./responses` на `@/shared/api/responses` +2. Создать `entities/session/api/sessionApi.test.ts` — скопировать из `api/auth.test.ts`, + заменить импорты +3. Создать `entities/session/model/sessionContext.ts` — SessionContext + SessionContextValue + (без Provider-логики, только тип и createContext) +4. Создать `entities/session/model/useSession.ts` — хук useContext с guard +5. Создать `entities/session/model/useSession.test.tsx` — скопировать из `hooks/useAuth.test.tsx`, + заменить импорты (тест пока оборачивает AuthProvider — будет работать через шим) +6. Создать `entities/session/index.ts` — barrel +7. Превратить `api/auth.ts` в shim +8. Превратить `hooks/useAuth.ts` в shim + +### Phase 2: app/providers/ + +1. Создать `app/providers/SessionProvider.tsx` — перенести логику из `context/AuthContext.tsx`: + - Импортирует sessionApi из entities/session + - Использует SessionContext из entities/session + - Весь lifecycle (refresh on mount, updateSession, clearSession) остаётся здесь +2. Создать `app/providers/AppProviders.tsx` — композиция QueryClientProvider + SessionProvider + (вынести QueryClient config из main.tsx) +3. Создать `app/providers/SessionProvider.test.tsx` — скопировать из `context/AuthContext.test.tsx`, + заменить импорты +4. Создать `app/providers/index.ts` — barrel +5. Превратить `context/AuthContext.tsx` в shim (реэкспорт SessionProvider и sessionContext) + +### Phase 3: app/routing/ + +1. Создать `app/routing/ProtectedRoute.tsx` — скопировать из `components/ProtectedRoute.tsx`, + заменить импорт useAuth на useSession из entities/session +2. Создать `app/routing/AppRoutes.tsx` — скопировать из `routes.tsx`: + - Заменить импорт Layout на AppLayout из app/layouts + - Заменить импорт ProtectedRoute на app/routing/ProtectedRoute + - Broker page импорты остаются без изменений +3. Создать `app/routing/index.ts` — barrel +4. Превратить `routes.tsx` в shim +5. Превратить `components/ProtectedRoute.tsx` в shim + +### Phase 4: app/layouts/ + +1. Создать `app/layouts/AppLayout.tsx` — скопировать из `components/Layout.tsx`: + - Заменить импорт useAuth на useSession из entities/session + - Заменить импорт SearchBar на @/components/SearchBar (SearchBar пока не мигрируется) +2. Создать `app/layouts/index.ts` — barrel +3. Превратить `components/Layout.tsx` в shim + +### Phase 5: app/App.tsx + +1. Создать `app/App.tsx` — из `App.tsx`, BrowserRouter + AppRoutes из app/routing +2. Создать `app/index.ts` — barrel +3. Превратить `App.tsx` в shim + +### Phase 6: main.tsx + +1. Обновить `main.tsx` — импортировать AppProviders из app/providers +2. Убрать прямой импорт QueryClientProvider и AuthProvider + +### Phase 7: update pages + +1. `pages/LoginPage.tsx` — заменить `../hooks/useAuth` на `@/entities/session` +2. `pages/RegisterPage.tsx` — аналогично +3. `pages/ProfilePage.tsx` — аналогично +4. Запустить тесты, lint, build + +## Ключевые решения + +### Почему `entities/session`, а не `features/auth` + +- User session — это бизнес-сущность (текущий пользователь, токен, статус аутентификации) +- Auth actions (login/register/logout) — это операции над сессией +- Provider-оркестрация вынесена в `app/providers/`, так как это инфраструктурный код + +### Почему context не в entities, а в app/providers + +- React-провайдер — это механизм связывания (DI/Infrastructure), а не бизнес-логика +- `SessionProvider` идёт в `app/`, где размещается вся корневая инфраструктура приложения +- SessionContext (тип + createContext) остаётся в `entities/session/model/`, чтобы entities + могли определять свой контракт + +### Coexistence + +Все legacy файлы остаются как re-export шимы, чтобы: +- Не ломать импорты в ещё не мигрированных файлах +- Позволить поэтапный переход +- Упростить удаление shim'ов после полной миграции diff --git a/docs/features/frontend-fsd-app-auth/spec.md b/docs/features/frontend-fsd-app-auth/spec.md new file mode 100644 index 0000000..e4235b0 --- /dev/null +++ b/docs/features/frontend-fsd-app-auth/spec.md @@ -0,0 +1,92 @@ +# Frontend FSD App + Auth + +Дата: 2026-06-20 +Статус: спецификация + +## Концепт + +Frontend проекта прошёл три итерации FSD-миграции: broker pilot, shared layer, entities migration. +Теперь существующие FSD-слои (entities, widgets, shared) не имеют соответствующей +инфраструктуры `app/` — корневого слоя приложения, в который выносятся entry point, routing, +провайдеры и layout. Параллельно auth-домен остаётся в legacy-структуре (`api/auth.ts`, +`context/AuthContext.tsx`, `hooks/useAuth.ts`), что не позволяет завершить вертикальный срез +auth/user session. + +## Цель + +Создать FSD-слой `app/` и мигрировать auth-домен в `entities/session/` и `app/providers/`, +завершив тем сквозной путь от корня приложения до domain entities через все FSD-слои. + +## Область изменений + +### Входит + +- Создание `app/` layer: entry point (`App.tsx`), routing (`AppRoutes`, `ProtectedRoute`), + providers (`SessionProvider`, `AppProviders`), layout (`AppLayout`) +- Создание `entities/session/` для auth-домена: session API, session context, useSession hook +- Перенос тестов вслед за кодом +- Превращение исходных legacy-файлов в re-export shims +- Обновление импортов в auth-страницах + +### Не входит + +- Миграция `api/screener.ts`, `hooks/useScreener.ts`, `components/screener/*` — следующий этап +- Миграция `api/broker.ts`, `api/broker.test.ts` — dead code, удаление в отдельной задаче +- Миграция `components/SearchBar.tsx`, `hooks/useSearch.ts` — следующий этап +- Миграция `components/portfolios/*`, `pages/portfolios/*` — следующий этап +- Миграция `pages/StockPage.tsx`, `pages/BondPage.tsx`, `pages/HomePage.tsx` — следующий этап +- Изменение backend-контрактов, Swagger, CI/CD +- Введение import guards или ESLint boundaries + +## Требования + +### 1. App-слой приложения + +- `app/App.tsx` — entry point: BrowserRouter + AppRoutes, без провайдеров +- `app/routing/AppRoutes.tsx` — все route definitions (аналог текущего `routes.tsx`) +- `app/routing/ProtectedRoute.tsx` — guard для аутентифицированных маршрутов +- `app/layouts/AppLayout.tsx` — корневой layout (header, навигация, outlet) +- `app/providers/AppProviders.tsx` — композиция корневых провайдеров: QueryClientProvider + SessionProvider +- `app/providers/SessionProvider.tsx` — провайдер сессии (управление жизненным циклом auth) +- `main.tsx` — использует AppProviders вместо ручной композиции + +### 2. Session-слой + +- `entities/session/api/sessionApi.ts` — чистые функции запросов + (login, register, refresh, logout, getMe, updateProfile) через shared/api/client +- `entities/session/model/sessionContext.ts` — SessionContext + SessionContextValue +- `entities/session/model/useSession.ts` — хук доступа к контексту +- `entities/session/index.ts` — barrel export + +### 3. Coexistence через shims + +Все старые файлы становятся re-export шимами: + +- `api/auth.ts` → shim для entities/session/api/sessionApi +- `hooks/useAuth.ts` → shim для entities/session/model/useSession +- `context/AuthContext.tsx` → shim для app/providers/SessionProvider + entities/session/model/sessionContext +- `routes.tsx` → shim для app/routing/AppRoutes +- `App.tsx` → shim для app/App +- `components/Layout.tsx` → shim для app/layouts/AppLayout +- `components/ProtectedRoute.tsx` → shim для app/routing/ProtectedRoute + +### 4. Тесты следуют за кодом + +- `api/auth.test.ts` → `entities/session/api/sessionApi.test.ts` +- `context/AuthContext.test.tsx` → `app/providers/SessionProvider.test.tsx` +- `hooks/useAuth.test.tsx` → `entities/session/model/useSession.test.tsx` + +### 5. Поведение не меняется + +Никакой функциональности не добавляется и не изменяется. Только перегруппировка кода. + +## Acceptance Criteria + +- `app/App.tsx`, `app/providers/`, `app/routing/`, `app/layouts/` существуют с указанной структурой +- `entities/session/` существует с api/model/index.ts +- Все тесты проходят (`npm test -w apps/frontend`) +- Lint проходит (`npm run lint -w apps/frontend`) +- Build проходит (`npm run build -w apps/frontend`) +- Все legacy файлы стали re-export шимами +- Экраны входа, регистрации и профиля работают как раньше +- Страницы брокера (защищённые) работают как раньше diff --git a/docs/features/frontend-fsd-app-auth/tasks.md b/docs/features/frontend-fsd-app-auth/tasks.md new file mode 100644 index 0000000..7a9430e --- /dev/null +++ b/docs/features/frontend-fsd-app-auth/tasks.md @@ -0,0 +1,57 @@ +# Frontend FSD App + Auth — Задачи + +## Phase 1: entities/session/ + +- [ ] 1.1 Создать `entities/session/api/sessionApi.ts` (6 функций из `api/auth.ts`, импорты через `@/shared/`) +- [ ] 1.2 Создать `entities/session/api/sessionApi.test.ts` (из `api/auth.test.ts`, обновить импорты) +- [ ] 1.3 Создать `entities/session/model/sessionContext.ts` (SessionContext + SessionContextValue) +- [ ] 1.4 Создать `entities/session/model/useSession.ts` (useContext с guard) +- [ ] 1.5 Создать `entities/session/model/useSession.test.tsx` (из `hooks/useAuth.test.tsx`, обновить импорты) +- [ ] 1.6 Создать `entities/session/index.ts` (barrel) +- [ ] 1.7 Превратить `api/auth.ts` в shim +- [ ] 1.8 Превратить `hooks/useAuth.ts` в shim +- [ ] 1.9 Проверить: `npm test -w apps/frontend` + +## Phase 2: app/providers/ + +- [ ] 2.1 Создать `app/providers/SessionProvider.tsx` (lifecycle из `context/AuthContext.tsx`) +- [ ] 2.2 Создать `app/providers/AppProviders.tsx` (QueryClientProvider + SessionProvider) +- [ ] 2.3 Создать `app/providers/SessionProvider.test.tsx` (из `context/AuthContext.test.tsx`) +- [ ] 2.4 Создать `app/providers/index.ts` (barrel) +- [ ] 2.5 Превратить `context/AuthContext.tsx` в shim +- [ ] 2.6 Проверить: `npm test -w apps/frontend` + +## Phase 3: app/routing/ + +- [ ] 3.1 Создать `app/routing/ProtectedRoute.tsx` (из `components/ProtectedRoute.tsx`, useSession) +- [ ] 3.2 Создать `app/routing/AppRoutes.tsx` (из `routes.tsx`, импорты из app/layouts, app/routing) +- [ ] 3.3 Создать `app/routing/index.ts` (barrel) +- [ ] 3.4 Превратить `routes.tsx` в shim +- [ ] 3.5 Превратить `components/ProtectedRoute.tsx` в shim +- [ ] 3.6 Проверить: `npm test -w apps/frontend` + +## Phase 4: app/layouts/ + +- [ ] 4.1 Создать `app/layouts/AppLayout.tsx` (из `components/Layout.tsx`, useSession) +- [ ] 4.2 Создать `app/layouts/index.ts` (barrel) +- [ ] 4.3 Превратить `components/Layout.tsx` в shim +- [ ] 4.4 Проверить: `npm test -w apps/frontend` + +## Phase 5: app/App.tsx + +- [ ] 5.1 Создать `app/App.tsx` (BrowserRouter + AppRoutes) +- [ ] 5.2 Создать `app/index.ts` (barrel) +- [ ] 5.3 Превратить `App.tsx` в shim +- [ ] 5.4 Проверить: `npm test -w apps/frontend` + +## Phase 6: main.tsx + +- [ ] 6.1 Обновить `main.tsx` (AppProviders вместо ручной композиции) +- [ ] 6.2 Проверить: `npm test -w apps/frontend` + +## Phase 7: update pages + +- [ ] 7.1 `pages/LoginPage.tsx` — импорт `@/entities/session` +- [ ] 7.2 `pages/RegisterPage.tsx` — импорт `@/entities/session` +- [ ] 7.3 `pages/ProfilePage.tsx` — импорт `@/entities/session` +- [ ] 7.4 Финальная проверка: `npm test -w apps/frontend && npm run lint -w apps/frontend && npm run build -w apps/frontend`