VEU.WathBody/1-docs/4-modules/m02-rest-api/api_specs/03-sync-post.md

5.5 KiB
Raw Permalink Blame History

Спецификация: POST /api/v1/sync (единица 3)

Формат пакета — module_reqs разд. 4 (копия 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": { } }
]

Ответы

Код Когда Тело
200 все записи пакета записаны { "accepted": ["guid1", ...] } — GUID пакета в исходном порядке
400 пакет > 50; неизвестная entity; op ≠ upsert; невалидный GUID; нарушение схемы/диапазонов; break c source = "auto"; невалидный JSON { "error": "<текст>" }
409 конфликт версии программы (UNIQUE(program_id, version)) { "error": "program_version_conflict" }
401 нет/чужой/заблокирован ключ (middleware) { "error": "unauthorized" }

Логика

  1. Десериализация и валидация пакета (все записи до начала записи); любая ошибка схемы → 400, в БД ничего не пишется.
  2. Одна транзакция на пакет (🛡 атомарность «все или ничего») — TransactionScope; Kit.Helpers открывает соединение на каждый вызов, Npgsql подключает их к ambient-транзакции. Все ЧТЕНИЯ (проверки 409, группы, существование workout'ов) выполняются до открытия транзакции — select-функции Kit открывают собственную транзакцию (вложенная запрещена); внутри — только записи:
    • записи применяются в порядке массива;
    • athlete_id для всех записей — из middleware (🛡, из тела не берётся).
  3. Маппинг (репозитории m01):
    • workout → WorkoutRepository.Insert (идемпотентен по uuid; если в data есть ended_at/status/avg_hr сверх вставки — следом Update для этих полей);
    • workout_set → WorkoutSetRepository.Insert (идемпотентен); workout_id должен принадлежать атлету — иначе исключение из функции БД → пакет отклоняется (400, текст ошибки функции);
    • exercise → ExerciseRepository.Insert (идемпотентен; group → group_id по коду через ExerciseGroupRepository);
    • program_version → при отсутствии программы атлета сначала ProgramRepository.Insert (title = «Программа»), затем ProgramVersionRepository.Insert (body — как есть); 409-проверка — в пре-проходе (п. 2): based_on_version из записи ≠ актуальная максимальная версия → 409 (телефон перечитывает программу); UNIQUE-конфликт в program_version_insert — тоже 409;
    • profile → AthleteProfileRepository.Upsert;
    • daily_log → DailyLogRepository.Upsert (по UNIQUE(athlete_id, date));
    • break → BreakRepository.Insert (source — только manual).
  4. 🛡 Идемпотентность: PK uuid сущностей = client_guid; повтор пакета — insert-функции возвращают существующие id, дублей нет; ответ — тот же accepted (для этого program_version_insert дополнен идемпотентностью по p_id — errata m01, 2026-10-18).
  5. Ошибка БД внутри транзакции → rollback → 400 (или 409 для конфликта версии программы), текст ошибки — в лог.

Тесты

# Проверка Ожидание
1 Валидный пакет (по одной записи каждой сущности) 200, accepted = все GUID, строки в БД
2 Повтор того же пакета 200, тот же accepted, дублей в БД нет
3 Пакет из 51 записи 400
4 Неизвестная entity 400
5 break с source = "auto" 400
6 rpe = 11 400, в БД ничего из пакета не записано
7 Вторая версия программы с тем же version (перезапись uuid) 409
8 workout_set с чужим workout_id (другой атлет) 400, пакет откатан
9 Пакет из 50 записей 200
10 program_version при отсутствии программы атлета 200; созданы program и program_version

Пример вызова

curl -X POST https://host/api/v1/sync -H "X-Api-Key: ivan-key-1" -H "Content-Type: application/json" \
  -d '[{"client_guid":"0b8f...","entity":"daily_log","op":"upsert",
        "data":{"date":"2026-10-17","bp_sys":128,"bp_dias":82,"weight_kg":84.5,"hr_resting":58,"wellbeing":4}}]'
# → {"accepted":["0b8f..."]}