# Требования к модулю: m08-admin-web > Модуль — административная консоль владельца (Ф-9, решение владельца > 2026-10-17): бэкенд — admin REST API `/api/admin/*` в > `Server.WatchBody.Web`; UI — Flutter Web-приложение > `3-app\3-web\admin-web` (SDK 3.47.7 — `C:\tools\flutter`), сборка > отдаётся сервером из `wwwroot\admin\`. Аутентификация владельца — > логин/пароль (`Admin.Login` / `Admin.PasswordHash`, PBKDF2) → > Bearer-токен сессии; атлеты к консоли доступа не имеют (🛡 > изменение vision 1.2). Самодостаточная копия требований (d-dev-6). ## 1. Назначение и границы **В границах модуля (backend REST):** - аутентификация: `POST /api/admin/login` (логин+пароль → `pbkdf2$...`-проверка → Bearer-токен, in-memory сессия, TTL 8 ч); все `/api/admin/*` кроме login — только с валидным токеном (401); - атлеты (Ф-9.1): список; создание (имя → сервер генерирует API-ключ, возвращается один раз, в БД — только хэш); смена ключа; блокировка/ разблокировка (заблокированный ключ → 401 на API телефонов); - тренировки (Ф-9.2): таблица по всем атлетам (фильтр атлет/дата, пагинация); карточка (подходы, план/факт, `facts_json`: таймлайн Lap + пульс Record, средний/пиковый пульс подхода, отдых); CSV-выгрузка текущей выборки (КУ-4, формат tech-req 2.4: `;`, UTF-8 BOM, `athlete;date;duration_min;avg_hr;sets;exercises; adherence_pct`); - программы (Ф-9.3): версии атлета (статусы/автор/даты/body); активация draft (ProgramStore.Activate); применение предложения правил из консоли — новая версия `author=admin` с телом rules-draft, затем активация (🛡 неизменяемость версений, Ф-9.3); - метрики (Ф-9.4): выборка `workout_metrics` атлета за период; - планы тренировок (Ф-9.5): сводка план/факт по неделям активной версии (запланировано сессий / выполнено / пропущено; доля); - отдача статики Flutter-сборки `wwwroot\admin\` (если каталог есть) — UI хостится тем же Kestrel. **Вне границ модуля:** Flutter Web UI `3-app\3-web\admin-web` (отдельный этап — требует Flutter SDK, решение гейта 1.3), редактор body версий (v1: просмотр + активация; правка — телефон m09/импорт m06), LLM-контур (v2), выгрузка АД-журнала CSV (v3). ## 2. Сессии и токены - пароль проверяется по `Admin.PasswordHash` (PBKDF2 100k, соль в хэше, префикс `pbkdf2$`; формат — `-salt` || PBKDF2-SHA256); несовпадение → 401 без указания, что именно неверно; - токен: 256-bit random (base64url), in-memory словарь `token → (expiresAt)`; перезапуск сервера — сессии сбрасываются (владелец входит заново); TTL 8 ч; logout — удаление из словаря; - 🛡 токены/пароли в логи не пишутся; `Admin.PasswordHash` не возвращается никаким эндпоинтом. ## 3. Инварианты (🛡 — нарушять запрещено) 1. **Только владелец** — `/api/admin/*` недоступен атлетам: их `X-Api-Key` не принимается, изолированный контур (`/api/v1/*` не меняется). 2. **Тайны** — API-ключ атлета возвращается только при создании/смене; хэши, пароли, токены — не возвращаются и не логируются. 3. **Запись только управляющая** — admin API не пишет факт-данные (тренировки/подходы/метрики): только атлеты (create/block/key), статусы версий программ и author=admin-копии предложений. 4. **Неизменяемость версий** — активация через ProgramStore; author=admin-копия создаётся CreateDraft, оригинал rules-draft не меняется. 5. **Формат CSV** — точно tech-req 2.4 (разделитель `;`, UTF-8 BOM). ## 4. Критерии приёмки модуля 1. Логин: верный пароль → токен (последующие запросы с Bearer — 200); неверный → 401; без токена/с токеном атлета (X-Api-Key) → 401. 2. Атлеты: создание возвращает ключ (в БД только хэш, повторный запрос ключ не отдаёт); смена ключа; блокировка → sync телефона с тем ключом получает 401; разблокировка → снова 200. 3. Тренировки: список всех атлетов с фильтрами/пагинацией; карточка с подходами и план/факт; CSV выборки соответствует формату. 4. Программы: версии атлета; активация draft меняет active (прежняя — archived); применение rules-предложения → активна версия author=admin с телом предложения. 5. Метрики и план/факт: выборка метрик за период; сводка недель (запланировано/выполнено/пропущено) сходится с фактическими тренировками. 6. Тесты интеграционные против docker `watchbody-pg` — зелёные; сборка решения без ошибок.