VEU.WathBody/1-docs/4-modules/m02-rest-api/module_reqs.md

10 KiB
Raw Blame History

Требования к модулю: m02-rest-api

Модуль — REST API сервера v1: проект Server.WatchBody.Web (ASP.NET Core, Kestrel — только localhost), middleware ключа атлета, POST /api/v1/sync, чтение тренировок и активной программы. Самодостаточная копия требований (d-dev-6): внешние документы продублированы по содержанию, ссылки — только для контекста.

1. Назначение и границы

В границах модуля:

  • проект Server.WatchBody.Web (net10.0, ASP.NET Core MVC) — каркас, конфигурация (appsettings.json, валидация при старте — tech-requirements 4.2: Api.*, Db.ConnectionString — фатально при неудаче), запуск по HTTPS-прокси снаружи (сам Kestrel — localhost); таблица маршрутов — fluent Kit.Helpers.Routes (Config\RouteConfig.cs, паттерн — Kit.Task.WebAp\Task.WebApp), обработчики — контроллеры Controllers\ (решение владельца 2026-10-18);
  • сопоставление атлетов конфига Api.Athletes ({name, apiKey}) с src.athlete при старте: upsert по SHA-256-хэшу ключа (идемпотентно; запись в БД — источник is_blocked);
  • middleware аутентификации X-Api-Key (см. разд. 3);
  • POST /api/v1/sync — приём пакета записей телефона (разд. 4);
  • GET /api/v1/workouts, GET /api/v1/workouts/{id}, GET /api/v1/programs/active (разд. 5);
  • интеграционные тесты эндпоинтов (WebApplicationFactory + тестовая БД).

Вне границ модуля: AdminWeb (m08: admin REST API /api/admin/* в этом же проекте + UI-приложение Flutter Web 3-app\3-web\admin-web — решение владельца 2026-10-17), fetch/import .fit (m06, m07), метрики/правила (m04, m05), ProgramStore как сервис правил (m03; здесь — только запись версии из sync), всё на телефоне и часах. Доступ к БД — только DAL-репозитории модуля m01 (Server.WatchBody.Core); прямой SQL в Web-проекте запрещён.

2. Стек и соглашения

  • C# / .NET 10, ASP.NET Core (minimal API), Server.WatchBody.Core (домен + репозитории m01) и Kit.Helpers (Log.Logger — файловые логи, уровень Info).
  • Конфигурация — appsettings.json (Api.Key, Api.Athletes[], Db.ConnectionString); валидация при старте, ошибки — в лог и stderr, код возврата 1.
  • Идентификация атлета — по хэшу ключа через AthleteRepository (athlete_select по p_api_key_hash); middleware кладёт athlete_id в HttpContext.Items.
  • Формат ответов — JSON (System.Text.Json, camelCase); ошибки — { "error": "<текст>" } с соответствующим кодом HTTP.

3. Аутентификация (requirements Ф-8.5, Ф-7.2 — 🛡)

  • Каждый запрос (кроме health-check GET /health) обязан нести заголовок X-Api-Key.
  • Сервер считает SHA-256 ключа и ищет атлета в src.athlete по api_key_hash. Ключ не найден или is_blocked = true → 401 (тело: {"error": "unauthorized"}), запрос не доходит до обработчиков.
  • 🛡 Атлета определяет только ключ: телефон не передаёт идентификатор атлета в данных пакета; athlete_id для всех записей пакета берётся из middleware (изоляция — каждый запрос видит данные только своего атлета).

4. POST /api/v1/sync (Ф-7.3, tech-requirements 2.1–2.2)

Запрос — JSON-массив записей (пакет ≤ 50), каждая:

{
  "client_guid": "uuid-v4",
  "entity": "workout | workout_set | exercise | program_version | profile | daily_log | break",
  "op": "upsert",
  "data": { "...схема сущности..." }
}

Ключевые поля data (tech-requirements 2.2):

  • exercise: exercise_id (uuid), name, group (push|pull|legs|core|cardio), is_base, is_active; повторная отправка упражнения с is_active=false — логическое удаление (src.exercise_delete, З-41(а): единственный серверный путь установки флага в v1; идемпотентно, GUID включается в accepted в обоих случаях, Ф-7.4);
  • workout: workout_id (uuid), started_at/ended_at (UTC ISO 8601), status (active|finished|cancelled), session_kind (normal|return|deload|test), program_version_id (nullable), avg_hr;
  • workout_set: workout_id, exercise_id, seq, weight_kg, reps, done_at, source (voice|watch|edit), in_plan, rpe (int? 1–10), knee_pain (int? 0–10, только «ноги»);
  • profile: sex, birth_year, weight_kg, hr_max (nullable);
  • break: kind (nullable), source (телефон передаёт только manual), started_on, planned_return, note;
  • daily_log: date (YYYY-MM-DD), bp_sys, bp_dias, weight_kg, hr_resting, wellbeing (1–5);
  • program_version: JSON-схема программы из дизайна LLM-планирования (разд. 2) — пишется в body как есть; athlete_id в теле не передаётся (выводится из ключа).

Правила обработки:

  1. Пакет атомарен — «все или ничего»: одна транзакция на пакет; при любой ошибке записи пакет целиком отклоняется.
  2. Успех → 200 { "accepted": ["guid1", ...] } — все GUID пакета.
  3. 🛡 Идемпотентность: повтор пакета безопасен — записи по client_guid (PK uuid) уже существуют, workout_insert и insert-функции uuid- сущностей возвращают существующий id; сервер снова отвечает тем же accepted (ответ идемпотентен).
  4. 409 — конфликт версии программы (UNIQUE(program_id, version) в program_version_insert); телефон перечитывает программу и перезаписывает локально. Тело: {"error": "program_version_conflict"}.
  5. break с source = "auto" от телефона отклоняется (400) — auto проставляет только сервер (Ф-14.1); аналогично прочие нарушения схем/диапазонов (rpe 1–10, wellbeing 1–5, пакет > 50, неизвестная entity) — 400 с текстом ошибки.
  6. Маппинг записей на функции БД (репозитории m01): workout → workout_insert/workout_update (update — при ended_at/status/avg_hr); workout_set → workout_set_insert; exercise → exercise_insert; program_version → program_version_insert (при первой версии атлета — program_insert); profile → athlete_profile_upsert; daily_log → daily_log_upsert; break → break_insert (source='manual').

5. Чтение (Ф-8.5)

Эндпоинт Ответ Реализация
GET /api/v1/workouts?limit=&offset= массив карточек тренировок (с подходами), пагинация (limit ≤ 100, умолчание 20) workout_select (карточки + sets агрегатом)
GET /api/v1/workouts/{id} полная карточка тренировки + command_log workout_get; чужой id → 404
GET /api/v1/programs/active активная версия программы атлета (с body) или 404 program_select + program_version_select (status=active) + program_version_get

Все выборки — в пределах athlete_id из middleware (🛡 изоляция).

6. Инварианты (🛡 — нарушать запрещено)

  1. Изоляция: каждый запрос видит и пишет данные только своего атлета (атлет — по X-Api-Key; в теле пакета атлет не передаётся).
  2. Идемпотентность: повтор пакета не создаёт дублей, ответ повторения — тот же accepted.
  3. Атомарность пакета: «все или ничего».
  4. API не редактирует факт-данные в обход sync: ручного ввода на сервере нет (Ф-8.6); пишет только через репозитории m01.
  5. break.source = 'auto' из sync не принимается.

7. Критерии приёмки модуля

  1. Без ключа / с чужим / заблокированным ключом — 401 на все эндпоинты; изоляция подтверждена тестом (атлет Б не видит данные атлета А, запись под чужой workout_id отклоняется).
  2. Пакет sync: успешный приём возвращает все GUID; повтор пакета — тот же ответ, дублей в БД нет; конфликт версии программы — 409; нарушение схемы — 400.
  3. GET workouts (пагинация), GET workouts/{id} (карточка, чужой id → 404), GET programs/active (или 404) работают в пределах атлета.
  4. Тесты эндпоинтов зелёные; валидация конфига при старте (пустой Api.Key, дубликаты ключей атлетов — код возврата 1).