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: Экран отрисовывает ленту: у системных рецептов заблокированы кнопки редактирования
Метод GET /api/v1/recipes
Документация API: Выгрузка и агрегация глобальной и кастомной кулинарной книги с поддержкой пагинации
- INVENTORY-2XX Backlog — Краткое Описание задачи 1.
- INVENTORY-1XX Refinement (Уточнение) — Краткое Описание задачи 2.
1 Функциональное назначение
Метод предназначен для вывода полного списка доступных рецептов на экране мобильного приложения Flutter. Он используется как обычными семьями (HOME), так и предприятиями (OFFICE) для поиска и выбора блюда перед запуском транзакционного контура готовки (Cook).
Метод решает три ключевые задачи:
- Гибридный UNION-вывод: Объединяет в единый упорядоченный список глобальные неизменяемые рецепты (
master_recipes), доступные всем пользователям платформы, и кастомные рецепты (user_custom_recipes), принадлежащие строго текущей группе (home_group_id). - Маркировка происхождения (
scope): Каждая запись в JSON-ответе помечается флагом происхождения ("SYSTEM"или"PERSONAL"). Это позволяет Flutter-интерфейсу блокировать кнопки редактирования/удаления для системных рецептов и активировать их для личных. - Безопасная пагинация (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 на уровне СУБД.
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"
}
]
}