Files
sfera-new/2025-09-18/AUTH_CONTEXT_MIGRATION_PLAN.md

18 KiB
Raw Blame History

🔐 ПЛАН БЕЗОПАСНОЙ МИГРАЦИИ НА 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 текущего состояния

    git add .
    git commit -m "backup: перед миграцией на AuthContext"
    git branch backup-before-auth-context
    
  2. Создать feature branch

    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)

    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)

    import { createContext } from 'react'
    import type { AuthContextType } from './types'
    
    export const AuthContext = createContext<AuthContextType | null>(null)
    
  3. Создать базовый Provider (src/contexts/auth/AuthProvider.tsx)

    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)

    // В начале файла добавляем
    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

    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

    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

    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. Оптимизировать рендеринг

    // Мемоизация значения контекста
    const value = useMemo(() => ({
      user,
      isAuthenticated,
      isLoading,
      checkAuth,
      logout,
      // ... другие методы
    }), [user, isAuthenticated, isLoading])
    
  3. Добавить DevTools

    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. Быстрый откат

    git checkout backup-before-auth-context
    
  2. Частичный откат

    • Установить USE_AUTH_CONTEXT = false
    • Вернуть проблемные компоненты на старую версию
    • Исправить проблемы в изолированной ветке
  3. Восстановление данных

    • localStorage сохраняется
    • Токены остаются валидными
    • Пользователи не заметят проблем

📝 Чек-лист готовности

Перед началом миграции убедитесь:

  • Создан backup текущего состояния
  • Команда предупреждена о работах
  • Подготовлен план коммуникации при проблемах
  • Есть доступ к логам и мониторингу
  • Определено время для миграции (лучше в период низкой активности)

🎯 Ожидаемый результат

После завершения миграции:

  1. Немедленные улучшения

    • Sidebar работает стабильно
    • Состояние синхронизировано между компонентами
    • Уменьшено количество запросов к API
  2. Долгосрочные преимущества

    • Готовность к добавлению новых функций
    • Упрощенная отладка
    • Лучшая производительность
    • Возможность добавления продвинутых функций (персистентность, refresh tokens)

Документ будет обновляться по ходу выполнения миграции