Skip to main content

Maintenance Mode

Система технического обслуживания для контролируемого отключения приложения.

Позволяет админам мгновенно заблокировать доступ к API, показать пользователям информативный оверлей и при этом сохранить возможность тестирования через bypass-список.


1. Summary

Goal: Безопасное отключение приложения для обновлений, миграций БД, исправления критических багов — без потери данных и с понятным UX для пользователей.

User Value: Вместо непонятных ошибок пользователь видит экран "Технические работы" с опциональным сообщением от администратора.


2. Business Logic

States

Maintenance mode имеет три состояния:

СостояниеisActivescheduledAtПоведение
IdlefalsenullПриложение работает нормально
ScheduledfalsedatetimeПоказывается предупреждающий баннер с обратным отсчётом
ActivetruenullВсе /api/* запросы блокируются (503), пользователи видят оверлей

Core Mechanics

1. Мгновенная активация

Админ нажимает "Включить" → isActive = true → middleware блокирует все /api/* запросы → пользователи видят оверлей.

2. Запланированное обслуживание

Админ выбирает время → scheduledAt = datetime → фронтенд показывает баннер "Плановое обслуживание через N мин" → при наступлении времени lazy auto-activation переводит в Active.

Lazy Auto-Activation

Нет cron-задачи. Когда любой запрос вызывает getState() и видит что scheduledAt <= now, сервис автоматически переключает isActive = true. Это избавляет от отдельного планировщика.

3. Деактивация

Админ нажимает "Отключить" → isActive = false, все поля обнуляются → middleware перестаёт блокировать → оверлей исчезает через polling (10 сек).

4. Bypass-список

Telegram ID в переменной окружения MAINTENANCE_BYPASS_TELEGRAM_IDS (через запятую) могут обходить блокировку для тестирования во время maintenance.

Bypass работает через заголовок

Middleware проверяет bypass по заголовку X-Telegram-Init-Data. Запросы без этого заголовка (raw fetch без initData) не могут быть идентифицированы как bypass — они будут заблокированы. Поэтому инфраструктурные endpoints исключены из middleware целиком (см. Architecture).

Frontend UI

Полноэкранный блокирующий оверлей — рендерится через createPortal на document.body.

  • Показывается когда isActive === true && bypassed === false
  • Блокирует скролл (useScrollLock)
  • Отображает кастомное сообщение или дефолтное "Ведутся технические работы"
  • Z-index: Z.MAINTENANCE (выше всех элементов приложения)

Admin UI

Status indicator в хедере админ-панели — зелёный/жёлтый/красный бейдж.

  • Быстрое включение/отключение одной кнопкой
  • Редактирование сообщения inline
  • Ссылка на расширенные настройки (/settings?tab=maintenance)

Polling Strategy

СостояниеrefetchIntervalЗачем
Active10 секБыстро убрать оверлей при деактивации
Scheduled30 секОбновлять обратный отсчёт
Idlefalse (не поллить)Экономия трафика

Frontend Integration (LoadingOrchestrator)

Early System Check — проверка maintenance выполняется первым шагом (STEP 0) в LoadingOrchestrator.initialize(), до загрузки Bootstrap.

Зачем: Предотвращает бесполезные запросы к БД (~8 запросов Bootstrap) во время технических работ, экономит ~100-200ms загрузки.

Как работает:

  1. LoadingOrchestrator устанавливает loadingState = 'checking_maintenance'
  2. Выполняется запрос к /api/maintenance/status (без auth)
  3. Если isActive === true && bypassed === false → показывается MaintenanceOverlay, инициализация останавливается
  4. Если isActive === false или bypassed === true → продолжается STEP 0.5 (season check)

Graceful Degradation:

try {
const status = await checkMaintenanceStatus();
if (status.isActive && !status.bypassed) {
// Блокируем дальнейшую загрузку
return;
}
} catch (error) {
// При ошибке проверки НЕ блокируем пользователя
console.warn('Maintenance check failed, continuing');
// Продолжаем к следующему шагу
}
Принцип Graceful Degradation

Если проверка maintenance упала с ошибкой (нет интернета, 500 от backend), пользователь не блокируется и может продолжить работу с приложением. Это предотвращает ситуацию, когда баг в проверке maintenance блокирует всех пользователей.

Файлы:

  • frontend/src/components/LoadingOrchestrator.tsx — функция checkMaintenanceStatus(), вызов в initialize()
  • frontend/src/types/loading.types.tschecking_maintenance loading state

Edge Cases

СитуацияПоведение
Юзер активен при включении maintenanceСледующий API запрос получит 503 → оверлей появится
Юзер пассивен (не делает запросов)Не узнает до следующего действия (polling отключен в idle)
Bypass-админ при активном maintenancebypassed: true в статусе → оверлей скрыт, API работает
Пустое сообщение от админаmessage = null → frontend показывает дефолтное "Ведутся технические работы"
Запланированное время наступилоLazy auto-activation при следующем getState() вызове
LoadingOrchestrator при активном maintenanceEarly check (STEP 0) блокирует Bootstrap → экономия ~8 DB запросов
Bootstrap при активном maintenanceBootstrap hydration содержит maintenance state без bypassed — поле приходит только из /api/maintenance/status

3. ADR (Architectural Decisions)

Singleton Prisma model

Проблема: Нужно хранить одно глобальное состояние maintenance.

Решение: Prisma модель MaintenanceMode с id = "singleton" и upsert паттерном.

Альтернативы (отклонены):

  • GlobalSettings (ещё одна таблица) — overengineering, у maintenance своя логика (scheduled, activatedBy)
  • Redis/env переменные — не персистентны, теряются при рестарте

Module-scope cache с 10s TTL

Проблема: getState() вызывается на каждый /api/* запрос через middleware.

Решение: In-memory cache с 10s TTL. Один DB-запрос на 10 секунд вместо тысяч.

Последствия: После activate/deactivate cache инвалидируется явно (invalidateCache()), задержка для пользователей — до 10 секунд.

Infrastructure endpoints excluded from middleware

Проблема: Onboarding flow (/api/telegram/*) использует raw fetch без X-Telegram-Init-Data заголовка. Maintenance middleware блокирует эти запросы, bypass-админы видят экран активации вместо приложения.

Решение: Исключить инфраструктурные prefix'ы из middleware:

  • /api/maintenance/* — polling статуса
  • /api/telegram/* — onboarding (bot check, session create/activate)

Почему не добавлять header: Это бы размазало фикс по множеству frontend-файлов и было бы хрупким — каждый новый raw fetch мог бы забыть header.

Middleware scope

Maintenance middleware применяется ТОЛЬКО к /api/* маршрутам. /admin/*, /rust/*, /health, /metrics, /onboarding/* — НЕ блокируются.

503 interceptor в apiRequest

Проблема: Как показать оверлей мгновенно, не дожидаясь polling?

Решение: apiRequest в api.service.ts перехватывает 503 с кодом MAINTENANCE_MODE, мгновенно обновляет React Query cache ['maintenance'] и бросает MaintenanceError.

Последствия: Оверлей появляется мгновенно при первом неудачном запросе, без ожидания polling-цикла.


4. Architecture

Request Flow

Key Components

КомпонентПутьОписание
Servicebackend/src/domains/maintenance/services/maintenance.service.tsCRUD + cache + lazy auto-activation
Middlewarebackend/src/domains/maintenance/middleware/maintenance.middleware.tsonRequest hook, bypass check
Status Routebackend/src/domains/maintenance/routes/maintenance-status.routes.tsPublic endpoint + bypass flag
Admin Controllerbackend/src/domains/maintenance/controllers/admin-maintenance.controller.tsAdmin API handler
Admin Routesbackend/src/domains/maintenance/routes/admin-maintenance.routes.tsAdmin endpoints registration
Schemasbackend/src/domains/maintenance/schemas/maintenance.schemas.tsFastify JSON schemas
Typesbackend/src/domains/maintenance/types/index.tsMaintenanceState, MaintenanceCacheEntry
Overlayfrontend/src/components/MaintenanceOverlay.tsxFull-screen blocking overlay (portal)
Bannerfrontend/src/components/MaintenanceBanner.tsxScheduled countdown banner
Hookfrontend/src/hooks/useMaintenance.tsTanStack Query + adaptive polling
Indicatoradmin/src/components/layout/MaintenanceIndicator.tsxAdmin header indicator
Settingsadmin/src/components/settings/MaintenanceSettings.tsxFull admin settings page

Middleware Scope

/api/*                    → BLOCKED during maintenance
/api/maintenance/* → EXCLUDED (infrastructure)
/api/telegram/* → EXCLUDED (onboarding)
/admin/* → NOT affected
/rust/* → NOT affected
/health, /metrics → NOT affected
/onboarding/* → NOT affected (not under /api/)

5. Database Schema

Models

МодельТаблицаОписание
MaintenanceModemaintenance_modeSingleton — глобальное состояние maintenance

Fields

ПолеТипОписание
idStringВсегда "singleton"
isActiveBooleanАктивен ли maintenance mode
messageString?Кастомное сообщение (null = дефолтное)
scheduledAtDateTime?Запланированное время активации
activatedAtDateTime?Когда был активирован
activatedByString?Telegram ID админа, активировавшего
updatedAtDateTimeAuto-updated

6. API Endpoints

МетодЭндпоинтОписаниеAuth
GET/api/maintenance/statusТекущее состояние + bypassed flagНет (public)

Response:

{
"success": true,
"data": {
"isActive": true,
"message": "Обновление базы данных",
"scheduledAt": null,
"activatedAt": "2025-01-15T10:00:00Z",
"bypassed": false
}
}

7. Configuration

Environment Variables

ПеременнаяОписаниеПример
MAINTENANCE_BYPASS_TELEGRAM_IDSComma-separated Telegram IDs для bypass123456789,987654321
Множественные ID

Через запятую, пробелы вокруг запятых допустимы: 123, 456, 789.