Метод GET /api/v1/recipes

Документация API: Выгрузка и агрегация глобальной и кастомной кулинарной книги с поддержкой пагинации

Published

July 2, 2026

1 Функциональное назначение

Метод предназначен для вывода полного списка доступных рецептов на экране мобильного приложения Flutter. Он используется как обычными семьями (HOME), так и предприятиями (OFFICE) для поиска и выбора блюда перед запуском транзакционного контура готовки (Cook).

Метод решает три ключевые задачи: 1. Гибридный UNION-вывод: Объединяет в единый упорядоченный список глобальные неизменяемые рецепты (master_recipes), доступные всем пользователям платформы, и кастомные рецепты (user_custom_recipes), принадлежащие строго текущей группе (home_group_id). 2. Маркировка происхождения (scope): Каждая запись в JSON-ответе помечается флагом происхождения ("SYSTEM" или "PERSONAL"). Это позволяет Flutter-интерфейсу блокировать кнопки редактирования/удаления для системных рецептов и активировать их для личных. 3. Безопасная пагинация (Safe Fetch): Ограничивает размер выборки через Query-параметры limit/offset (Максимум = 100), предотвращая просадки производительности СУБД.

2 Протокол взаимодействия (HTTP Контракт)

  • Метод: GET
  • Маршрут: /api/v1/recipes
  • Формат данных: application/json (только ответ)

2.1 Спецификация параметров запроса (Query Parameters)

Параметр Тип Обязательный Описание Пример значения
limit Integer Нет Количество возвращаемых рецептов (Максимум = 100) 20
offset Integer Нет Смещение для постраничного бесконечного скролла 0

2.2 Спецификация заголовков (HTTP Headers)

Заголовок Обязательный Описание Пример значения
Authorization Да Токен авторизации (Access Token). Бэкенд извлекает из него home_group_id. Bearer eyJhbGciOiJIUzI1Ni...
X-Request-ID Да Сквозной ID запроса для замера скорости тяжелого объединяющего запроса req-rec-read-55bb

2.3 Спецификация структуры ответа (Response Body)

Метод возвращает плоский массив шапок доступных рецептов. Вложенная матрица ингредиентов и пошаговые инструкции здесь не выгружаются для экономии трафика; для просмотра состава используется точечный gRPC-канал или отдельный детальный эндпоинт.

Поле Тип Описание Пример значения
recipe_id Integer Уникальный ID рецепта в рамках его таблицы 412
scope String Контекст происхождения рецепта (SYSTEM или PERSONAL) "PERSONAL"
title String Название кулинарного блюда "Бабушкин яблочный пирог"

2.3.1 Пример JSON-ответа (Payload):

{
  "home_group_id": "group-772-alpha",
  "total_count": 142,
  "limit": 20,
  "offset": 0,
  "recipes": [
    {
      "recipe_id": 412,
      "scope": "PERSONAL",
      "title": "Бабушкин яблочный пирог",
      "created_at": "2026-07-01T23:28:00Z"
    },
    {
      "recipe_id": 12,
      "scope": "SYSTEM",
      "title": "Классическая лазанья с мясным фаршем",
      "created_at": "2026-01-01T10:00:00Z"
    }
  ]
}

3 Схема обработки запроса пользователя (Mermaid)

На диаграмме представлена логика сквозного параллельного извлечения данных из таблиц глобальных общесистемных рецептов и приватных кулинарных книг конкретной группы пользователя с использованием оператора UNION ALL на уровне СУБД.

sequenceDiagram
    autonumber
    actor User as Пользователь (Экран Книги)
    participant APP as Мобильное приложение (Flutter)
    participant RS as Сервис Рецептов (FastAPI)
    participant DB as База Данных (PostgreSQL)

    User->>APP: Переходит во вкладку "Все рецепты"
    APP->>RS: HTTP GET /api/v1/recipes?limit=20 (JWT в Header)
    activate RS
    
    note over RS: Шаг 3: Чтение home_group_id из клеймов JWT-токена
    
    RS->>DB: Вызов SQL-запроса UNION по home_group_id с пагинацией
    activate DB
    note over DB: Шаг 5: Выборка из master_recipes со scope='SYSTEM'
    note over DB: Шаг 6: Выборка из user_custom_recipes по home_group_id со scope='PERSONAL'
    note over DB: Шаг 7: Объединение, сортировка по дате и LIMIT 20
    DB-->>RS: Возвращает агрегированный плоский массив шапок рецептов
    deactivate DB
    
    RS-->>APP: HTTP 200 OK (JSON со списком гибридных рецептов)
    deactivate RS
    note over APP: Экран отрисовывает ленту: у системных рецептов заблокированы кнопки редактирования

4 Расшифровка шагов

  • Шаг 1 (User -> APP): Пользователь (в домашнем контуре или сотрудник офиса) открывает раздел кулинарной книги во Flutter-приложении. Мобильное приложение мгновенно инициирует фоновый HTTP-запрос для вывода доступных рецептов.
  • Шаг 2 (APP -> RS): Приложение отправляет запрос HTTP GET /api/v1/recipes?limit=20&offset=0. Параметры пагинации передаются в Query String. В заголовке Authorization прокидывается Access Token.
  • Шаг 3 (RS -> RS): Микросервис Рецептов (FastAPI) расшифровывает JWT-токен и извлекает параметр home_group_id, который гарантирует, что пользователь увидит личные рецепты только своей группы, изолированные от других участников платформы.
  • Шаг 4 (RS -> DB): Шлюз отправляет в базу данных PostgreSQL оптимизированный SQL-запрос, связывающий две независимые таблицы через механизм сквозного объединения.
  • Шаг 5 (DB -> DB): На первом этапе СУБД сканирует таблицу глобального справочника системы master_recipes. Из неё извлекаются общедоступные записи и жестко маркируются текстовой константой 'SYSTEM' в поле scope.
  • Шаг 6 (DB -> DB): На втором этапе СУБД сканирует таблицу кастомного инвентаря user_custom_recipes, применяя жесткий фильтр по индексу home_group_id = $1. Полученные строки маркируются текстовой константой 'PERSONAL' в поле scope.
  • Шаг 7 (DB -> DB): СУБД выполняет оператор UNION ALL, склеивая оба потока данных в единую плоскую таблицу. К итоговому набору применяется глобальная сортировка ORDER BY created_at DESC, а также переданные параметры безопасности LIMIT 20 OFFSET 0.
  • Шаг 8 (DB -> RS): База данных возвращает бэкенду отсортированный гибридный массив шапок рецептов, не превышающий лимит размера страницы.
  • Шаг 9 (RS -> APP): FastAPI упаковывает результат в Pydantic-модель списка и возвращает клиенту статус HTTP 200 OK. Мобильное приложение Flutter принимает JSON-пакет. Стейт-менеджер перерисовывает экран: благодаря флагу scope интерфейс четко разграничивает системные карточки и личные шаблоны, блокируя элементы управления (кнопки «Изменить», «Удалить») на системных рецептах.

5 Спецификация серверной логики (SQL) и вилок исключений

5.1 1. Архитектурный SQL-запрос сквозного объединения (Шаги 5–7)

Для реализации бизнес-логики (параллельное извлечение общесистемных рецептов и приватных кулинарных книг конкретной группы в единую ленту выдачи) на уровне PostgreSQL используется оператор UNION ALL.

(
    -- Шаг 5: Выгрузка глобальных общесистемных рецептов
    SELECT 
        id AS recipe_id,
        'SYSTEM' AS scope, -- Хардкод-маркер происхождения для UI Flutter
        title,
        created_at
    FROM master_recipes
)
UNION ALL
(
    -- Шаг 6: Выгрузка уникальных кастомных рецептов конкретной группы пользователя
    SELECT 
        id AS recipe_id,
        'PERSONAL' AS scope, -- Хардкод-маркер происхождения для UI Flutter
        title,
        created_at
    FROM user_custom_recipes
    WHERE home_group_id = \$1 -- Multi-Tenancy изоляция кулинарных книг
)
-- Шаг 7: Глобальная сортировка объединенного потока данных и пагинация
ORDER BY created_at DESC
LIMIT \$2 
OFFSET \$3;

5.2 2. Спецификация ответов сервера (Response Body)

5.2.1 HTTP 200 OK (Ответ на Шаге 9)

Возвращается при успешной выборке гибридного реестра кулинарной книги. Из соображений экономии трафика тяжелые массивы cooking_steps и ingredients на этом этапе не запрашиваются.

{
  "home_group_id": "group-772-alpha",
  "total_count": 2,
  "limit": 20,
  "offset": 0,
  "recipes": [
    {
      "recipe_id": 412,
      "scope": "PERSONAL",
      "title": "Бабушкин яблочный пирог",
      "created_at": "2026-07-01T23:28:00Z"
    },
    {
      "recipe_id": 12,
      "scope": "SYSTEM",
      "title": "Классическая лазанья с мясным фаршем",
      "created_at": "2026-01-01T10:00:00Z"
    }
  ]
}

5.3 3. Вилки исключений и обработка ошибок

5.3.1 Ошибка: Превышение системного лимита размера страницы (HTTP 400 Bad Request)

Выбрасывается на Шаге 3 бэкенд-шлюзом FastAPI, если клиент передал в Query-параметрах limit со значением больше 100, нарушая архитектурный стандарт безопасности API.

{
  "error_code": "ERR-RECIPES-FETCH-LIMIT-EXCEEDED",
  "message": "Превышен максимальный лимит размера страницы (максимум — 100 рецептов за один запрос).",
  "details": {
    "requested_limit": 500,
    "allowed_max_limit": 100
  }
}

5.3.2 Ошибка: Невалидный формат смещения пагинации (HTTP 422 Unprocessable Entity)

Выбрасывается, если клиент передал в Query-параметрах отрицательное значение смещения (offset=-50), нарушающее встроенную валидацию типов Pydantic.

{
  "error_code": "ERR-VALIDATION-FAILED",
  "message": "Передан некорректный параметр смещения offset страницы.",
  "details": [
    {
      "loc": ["query", "offset"],
      "msg": "Input should be greater than or equal to 0",
      "type": "less_than_equal_to_failed"
    }
  ]
}