179 lines
9.0 KiB
Markdown
179 lines
9.0 KiB
Markdown
# 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'ов после полной миграции
|