docs: добавить планы улучшения архитектуры SFERA
This commit is contained in:
505
2025-09-18/AUTH_CONTEXT_MIGRATION_PLAN.md
Normal file
505
2025-09-18/AUTH_CONTEXT_MIGRATION_PLAN.md
Normal file
@ -0,0 +1,505 @@
|
||||
# 🔐 ПЛАН БЕЗОПАСНОЙ МИГРАЦИИ НА AuthContext
|
||||
|
||||
**Дата создания:** 2025-09-18
|
||||
**Автор:** Claude AI + Вероника Смирнова
|
||||
**Статус:** Готов к реализации
|
||||
|
||||
## 📊 Результаты глубокой диагностики
|
||||
|
||||
### Анализ текущей реализации useAuth
|
||||
|
||||
**Размер и сложность:**
|
||||
- 657 строк кода
|
||||
- 65 файлов используют useAuth()
|
||||
- Сложная логика с rollback механизмами
|
||||
- Интеграция с Apollo Client через localStorage
|
||||
|
||||
**Ключевые компоненты:**
|
||||
1. **State Management**
|
||||
- `user: User | null` - данные пользователя
|
||||
- `isAuthenticated: boolean` - статус авторизации
|
||||
- `isLoading: boolean` - индикатор загрузки
|
||||
- `isCheckingAuth: boolean` - защита от дублирования
|
||||
|
||||
2. **Методы авторизации**
|
||||
- `sendSmsCode` - отправка SMS
|
||||
- `verifySmsCode` - проверка кода
|
||||
- `checkAuth` - проверка текущей сессии
|
||||
- `logout` - выход
|
||||
|
||||
3. **Методы регистрации**
|
||||
- `registerFulfillmentOrganization`
|
||||
- `registerSellerOrganization`
|
||||
- `registerOrganization` (универсальный)
|
||||
|
||||
4. **Интеграции**
|
||||
- Apollo Client для GraphQL
|
||||
- localStorage для токенов
|
||||
- refreshApolloClient для синхронизации
|
||||
|
||||
### Выявленные проблемы
|
||||
|
||||
1. **Множественные экземпляры состояния**
|
||||
```
|
||||
AppShell → useAuth() → useState (копия 1)
|
||||
Sidebar → useAuth() → useState (копия 2)
|
||||
Component → useAuth() → useState (копия 3)
|
||||
```
|
||||
|
||||
2. **Race conditions**
|
||||
- checkAuth вызывается параллельно из разных компонентов
|
||||
- isCheckingAuth защищает только локальный экземпляр
|
||||
|
||||
3. **Отсутствие синхронизации**
|
||||
- Обновления в одном компоненте не видны в других
|
||||
- GET_ME выполняется многократно
|
||||
|
||||
4. **Проблемы с SSR**
|
||||
- Прямое обращение к localStorage
|
||||
- window checks разбросаны по коду
|
||||
|
||||
## 🎯 Архитектура решения
|
||||
|
||||
### Новая структура с AuthContext
|
||||
|
||||
```
|
||||
AuthProvider (глобальное состояние)
|
||||
├── Apollo Provider
|
||||
│ └── Auth Link (токены из контекста)
|
||||
├── State Management
|
||||
│ ├── user
|
||||
│ ├── isAuthenticated
|
||||
│ └── isLoading
|
||||
└── Methods
|
||||
├── Authentication
|
||||
├── Registration
|
||||
└── Session Management
|
||||
```
|
||||
|
||||
### Преимущества
|
||||
|
||||
1. **Единое состояние** - все компоненты видят одни данные
|
||||
2. **Оптимизация запросов** - GET_ME выполняется 1 раз
|
||||
3. **Синхронизация** - изменения видны везде мгновенно
|
||||
4. **SSR совместимость** - централизованные проверки
|
||||
5. **Типобезопасность** - строгая типизация контекста
|
||||
|
||||
## 📋 Поэтапный план миграции
|
||||
|
||||
### ЭТАП 0: Подготовка [30 мин]
|
||||
|
||||
**Цель:** Создать безопасную среду для миграции
|
||||
|
||||
1. **Создать backup текущего состояния**
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "backup: перед миграцией на AuthContext"
|
||||
git branch backup-before-auth-context
|
||||
```
|
||||
|
||||
2. **Создать feature branch**
|
||||
```bash
|
||||
git checkout -b feature/auth-context-migration
|
||||
```
|
||||
|
||||
3. **Подготовить структуру папок**
|
||||
```
|
||||
src/
|
||||
├── contexts/
|
||||
│ └── auth/
|
||||
│ ├── AuthContext.tsx # Основной контекст
|
||||
│ ├── AuthProvider.tsx # Provider компонент
|
||||
│ ├── types.ts # TypeScript типы
|
||||
│ └── utils.ts # Вспомогательные функции
|
||||
```
|
||||
|
||||
### ЭТАП 1: Создание AuthContext с минимальной функциональностью [1 час]
|
||||
|
||||
**Цель:** Создать работающий контекст без нарушения существующего функционала
|
||||
|
||||
1. **Создать типы** (`src/contexts/auth/types.ts`)
|
||||
```typescript
|
||||
export interface User {
|
||||
id: string
|
||||
phone: string
|
||||
avatar?: string
|
||||
managerName?: string
|
||||
organization?: Organization
|
||||
}
|
||||
|
||||
export interface Organization {
|
||||
id: string
|
||||
inn: string
|
||||
type: 'FULFILLMENT' | 'SELLER' | 'LOGIST' | 'WHOLESALE'
|
||||
// ... остальные поля
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
user: User | null
|
||||
isAuthenticated: boolean
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
export interface AuthContextType extends AuthState {
|
||||
// Методы будем добавлять постепенно
|
||||
checkAuth: () => Promise<void>
|
||||
logout: () => void
|
||||
}
|
||||
```
|
||||
|
||||
2. **Создать контекст** (`src/contexts/auth/AuthContext.tsx`)
|
||||
```typescript
|
||||
import { createContext } from 'react'
|
||||
import type { AuthContextType } from './types'
|
||||
|
||||
export const AuthContext = createContext<AuthContextType | null>(null)
|
||||
```
|
||||
|
||||
3. **Создать базовый Provider** (`src/contexts/auth/AuthProvider.tsx`)
|
||||
```typescript
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
import { AuthContext } from './AuthContext'
|
||||
import { getAuthToken, removeAuthToken } from '@/lib/apollo-client'
|
||||
|
||||
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
const [user, setUser] = useState<User | null>(null)
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
|
||||
// Минимальная реализация checkAuth
|
||||
const checkAuth = useCallback(async () => {
|
||||
const token = getAuthToken()
|
||||
if (!token) {
|
||||
setIsAuthenticated(false)
|
||||
setUser(null)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Добавить GET_ME запрос
|
||||
setIsLoading(false)
|
||||
}, [])
|
||||
|
||||
// Минимальная реализация logout
|
||||
const logout = useCallback(() => {
|
||||
removeAuthToken()
|
||||
setUser(null)
|
||||
setIsAuthenticated(false)
|
||||
window.location.href = '/'
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
checkAuth()
|
||||
}, [checkAuth])
|
||||
|
||||
const value = {
|
||||
user,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
checkAuth,
|
||||
logout
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={value}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
4. **Создать временный хук-обертку** (`src/hooks/useAuth.ts`)
|
||||
```typescript
|
||||
// В начале файла добавляем
|
||||
import { useContext } from 'react'
|
||||
import { AuthContext } from '@/contexts/auth/AuthContext'
|
||||
|
||||
// Временный флаг для постепенной миграции
|
||||
const USE_AUTH_CONTEXT = false
|
||||
|
||||
export const useAuth = (): UseAuthReturn => {
|
||||
if (USE_AUTH_CONTEXT) {
|
||||
const context = useContext(AuthContext)
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within AuthProvider')
|
||||
}
|
||||
|
||||
// Адаптер для совместимости API
|
||||
return {
|
||||
...context,
|
||||
// Методы-заглушки для совместимости
|
||||
sendSmsCode: async () => ({ success: false, message: 'Not implemented' }),
|
||||
verifySmsCode: async () => ({ success: false, message: 'Not implemented' }),
|
||||
registerFulfillmentOrganization: async () => ({ success: false, message: 'Not implemented' }),
|
||||
registerSellerOrganization: async () => ({ success: false, message: 'Not implemented' }),
|
||||
registerOrganization: async () => ({ success: false, message: 'Not implemented' }),
|
||||
updateUser: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Существующая реализация остается без изменений
|
||||
// ... весь текущий код
|
||||
}
|
||||
```
|
||||
|
||||
### ЭТАП 2: Тестирование базовой интеграции [30 мин]
|
||||
|
||||
**Цель:** Убедиться что ничего не сломалось
|
||||
|
||||
1. **Добавить AuthProvider в providers.tsx**
|
||||
```typescript
|
||||
import { AuthProvider } from '@/contexts/auth/AuthProvider'
|
||||
|
||||
export function Providers({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<ApolloProvider client={apolloClient}>
|
||||
<AuthProvider>
|
||||
<SidebarProvider>
|
||||
{children}
|
||||
</SidebarProvider>
|
||||
</AuthProvider>
|
||||
</ApolloProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
2. **Включить USE_AUTH_CONTEXT для одного компонента**
|
||||
- Начать с простого компонента (например, UserProfile в sidebar)
|
||||
- Проверить что компонент рендерится
|
||||
- Проверить что нет ошибок в консоли
|
||||
|
||||
3. **Rollback план**
|
||||
- Если есть ошибки - установить USE_AUTH_CONTEXT = false
|
||||
- Исправить проблемы
|
||||
- Повторить тестирование
|
||||
|
||||
### ЭТАП 3: Миграция основного функционала [2 часа]
|
||||
|
||||
**Цель:** Перенести всю логику в AuthContext
|
||||
|
||||
1. **Перенести checkAuth с GET_ME**
|
||||
```typescript
|
||||
const checkAuth = useCallback(async () => {
|
||||
if (isCheckingAuth.current) return
|
||||
|
||||
const token = getAuthToken()
|
||||
if (!token) {
|
||||
setIsAuthenticated(false)
|
||||
setUser(null)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
isCheckingAuth.current = true
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
const { data } = await apolloClient.query({
|
||||
query: GET_ME,
|
||||
errorPolicy: 'all',
|
||||
fetchPolicy: 'network-only'
|
||||
})
|
||||
|
||||
if (data?.me) {
|
||||
setUser(data.me)
|
||||
setIsAuthenticated(true)
|
||||
setUserData(data.me)
|
||||
}
|
||||
} catch (error) {
|
||||
// Обработка ошибок
|
||||
} finally {
|
||||
isCheckingAuth.current = false
|
||||
setIsLoading(false)
|
||||
}
|
||||
}, [])
|
||||
```
|
||||
|
||||
2. **Перенести SMS методы**
|
||||
- sendSmsCode
|
||||
- verifySmsCode
|
||||
- Сохранить всю логику с логированием
|
||||
|
||||
3. **Перенести методы регистрации**
|
||||
- registerFulfillmentOrganization
|
||||
- registerSellerOrganization
|
||||
- registerOrganization
|
||||
- Сохранить rollback механизмы
|
||||
|
||||
4. **Добавить updateUser**
|
||||
```typescript
|
||||
const updateUser = useCallback((updatedUser: Partial<User>) => {
|
||||
setUser(current => {
|
||||
if (!current) return current
|
||||
const updated = { ...current, ...updatedUser }
|
||||
setUserData(updated) // Синхронизация с localStorage
|
||||
return updated
|
||||
})
|
||||
}, [])
|
||||
```
|
||||
|
||||
### ЭТАП 4: Постепенная миграция компонентов [1 день]
|
||||
|
||||
**Цель:** Безопасно перевести все компоненты на новую систему
|
||||
|
||||
1. **Приоритетные компоненты** (первая очередь)
|
||||
- AppShell
|
||||
- Sidebar и все его варианты
|
||||
- AuthGuard
|
||||
|
||||
2. **Критические компоненты** (вторая очередь)
|
||||
- Страницы авторизации (login, register)
|
||||
- DashboardHome
|
||||
- useRoleGuard
|
||||
|
||||
3. **Остальные компоненты** (третья очередь)
|
||||
- Разбить на группы по 10-15 файлов
|
||||
- Мигрировать группами
|
||||
- Тестировать после каждой группы
|
||||
|
||||
**Процесс для каждого компонента:**
|
||||
1. Включить USE_AUTH_CONTEXT локально
|
||||
2. Проверить функциональность
|
||||
3. Если работает - коммит
|
||||
4. Если нет - откат и исправление
|
||||
|
||||
### ЭТАП 5: Оптимизация и очистка [1 час]
|
||||
|
||||
**Цель:** Удалить старый код и оптимизировать
|
||||
|
||||
1. **Удалить старую реализацию из useAuth**
|
||||
- Оставить только обертку для контекста
|
||||
- Удалить локальные useState
|
||||
- Удалить дублированную логику
|
||||
|
||||
2. **Оптимизировать рендеринг**
|
||||
```typescript
|
||||
// Мемоизация значения контекста
|
||||
const value = useMemo(() => ({
|
||||
user,
|
||||
isAuthenticated,
|
||||
isLoading,
|
||||
checkAuth,
|
||||
logout,
|
||||
// ... другие методы
|
||||
}), [user, isAuthenticated, isLoading])
|
||||
```
|
||||
|
||||
3. **Добавить DevTools**
|
||||
```typescript
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
(window as any).__AUTH_STATE__ = { user, isAuthenticated }
|
||||
}
|
||||
```
|
||||
|
||||
### ЭТАП 6: Финальное тестирование [1 час]
|
||||
|
||||
**Цель:** Убедиться что все работает
|
||||
|
||||
1. **Функциональные тесты**
|
||||
- [ ] Авторизация по SMS
|
||||
- [ ] Регистрация всех типов организаций
|
||||
- [ ] Отображение sidebar
|
||||
- [ ] Переходы между страницами
|
||||
- [ ] Выход из системы
|
||||
- [ ] Обновление страницы (F5)
|
||||
|
||||
2. **Тесты производительности**
|
||||
- [ ] Нет множественных GET_ME запросов
|
||||
- [ ] Нет лишних ре-рендеров
|
||||
- [ ] Быстрая загрузка после F5
|
||||
|
||||
3. **Регрессионные тесты**
|
||||
- [ ] Все 65 компонентов работают
|
||||
- [ ] API ключи сохраняются
|
||||
- [ ] Роутинг по типам организаций
|
||||
|
||||
## 🚨 Риски и митигация
|
||||
|
||||
### Риск 1: Поломка авторизации
|
||||
**Митигация:**
|
||||
- Постепенная миграция через флаг USE_AUTH_CONTEXT
|
||||
- Возможность быстрого отката
|
||||
- Тестирование на каждом этапе
|
||||
|
||||
### Риск 2: Потеря состояния
|
||||
**Митигация:**
|
||||
- Сохранение в localStorage остается
|
||||
- Rollback механизмы сохраняются
|
||||
- Логирование всех изменений
|
||||
|
||||
### Риск 3: Проблемы с SSR
|
||||
**Митигация:**
|
||||
- Все проверки window в одном месте
|
||||
- useEffect для клиентских операций
|
||||
- Правильная инициализация состояния
|
||||
|
||||
### Риск 4: Race conditions
|
||||
**Митигация:**
|
||||
- useRef для флагов загрузки
|
||||
- Отмена дублированных запросов
|
||||
- Правильная очередность операций
|
||||
|
||||
## 📊 Метрики успеха
|
||||
|
||||
1. **Функциональность**
|
||||
- ✅ Все 65 компонентов работают
|
||||
- ✅ Sidebar отображается корректно
|
||||
- ✅ Авторизация стабильна
|
||||
|
||||
2. **Производительность**
|
||||
- ✅ GET_ME вызывается 1 раз
|
||||
- ✅ Нет задержек при навигации
|
||||
- ✅ Быстрая загрузка после F5
|
||||
|
||||
3. **Качество кода**
|
||||
- ✅ Единое место управления состоянием
|
||||
- ✅ Типобезопасность
|
||||
- ✅ Отсутствие дублирования
|
||||
|
||||
## 🔄 Rollback план
|
||||
|
||||
Если что-то пойдет не так на любом этапе:
|
||||
|
||||
1. **Быстрый откат**
|
||||
```bash
|
||||
git checkout backup-before-auth-context
|
||||
```
|
||||
|
||||
2. **Частичный откат**
|
||||
- Установить USE_AUTH_CONTEXT = false
|
||||
- Вернуть проблемные компоненты на старую версию
|
||||
- Исправить проблемы в изолированной ветке
|
||||
|
||||
3. **Восстановление данных**
|
||||
- localStorage сохраняется
|
||||
- Токены остаются валидными
|
||||
- Пользователи не заметят проблем
|
||||
|
||||
## 📝 Чек-лист готовности
|
||||
|
||||
Перед началом миграции убедитесь:
|
||||
|
||||
- [ ] Создан backup текущего состояния
|
||||
- [ ] Команда предупреждена о работах
|
||||
- [ ] Подготовлен план коммуникации при проблемах
|
||||
- [ ] Есть доступ к логам и мониторингу
|
||||
- [ ] Определено время для миграции (лучше в период низкой активности)
|
||||
|
||||
## 🎯 Ожидаемый результат
|
||||
|
||||
После завершения миграции:
|
||||
|
||||
1. **Немедленные улучшения**
|
||||
- Sidebar работает стабильно
|
||||
- Состояние синхронизировано между компонентами
|
||||
- Уменьшено количество запросов к API
|
||||
|
||||
2. **Долгосрочные преимущества**
|
||||
- Готовность к добавлению новых функций
|
||||
- Упрощенная отладка
|
||||
- Лучшая производительность
|
||||
- Возможность добавления продвинутых функций (персистентность, refresh tokens)
|
||||
|
||||
---
|
||||
|
||||
*Документ будет обновляться по ходу выполнения миграции*
|
Reference in New Issue
Block a user