Метод GET /api/v1/menu/templates

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

Published

July 2, 2026

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

Метод предназначен для вывода полного списка доступных комплексных меню на экране мобильного приложения Flutter. Он используется сотрудниками и менеджерами предприятий для выбора сета перед обедом или для администрирования.

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

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

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

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

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

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

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

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

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

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

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

{
  "home_group_id": "company-office-center",
  "total_count": 15,
  "limit": 20,
  "offset": 0,
  "templates": [
    {
      "template_id": 1,
      "scope": "SYSTEM",
      "menu_title": "Завтрак Фитнес: Овсянка и Ягоды",
      "created_at": "2026-01-01T10:00:00Z"
    },
    {
      "template_id": 102,
      "scope": "COMPANY",
      "menu_title": "Пятничный Бургер-День v2",
      "created_at": "2026-07-01T23:28:00Z"
    }
  ]
}

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

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

sequenceDiagram
    autonumber
    actor Employee as Сотрудник / Менеджер
    participant APP as Мобильное приложение (Flutter)
    participant MS as Сервис Меню (FastAPI)
    participant DB as База Данных (PostgreSQL)

    Employee->>APP: Переходит на экран "Выбор Меню / Обеды"
    APP->>MS: HTTP GET /api/v1/menu/templates?limit=20 (JWT в Header)
    activate MS
    
    note over MS: Шаг 3: Валидация JWT (account_type == OFFICE)
    
    MS->>DB: Вызов SQL-запроса UNION по home_group_id с пагинацией
    activate DB
    note over DB: Шаг 5: Выборка из master_menu_templates со scope='SYSTEM'
    note over DB: Шаг 6: Выборка из company_custom_menus по home_group_id со scope='COMPANY'
    note over DB: Шаг 7: Объединение, сортировка по дате и LIMIT 20
    DB-->>MS: Возвращает агрегированный плоский массив шапок меню
    deactivate DB
    
    MS-->>APP: HTTP 200 OK (JSON со списком гибридных шаблонов)
    deactivate API
    note over APP: Экран отрисовывает карточки: у системных меню заблокированы кнопки "Удалить/Изменить"

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

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

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

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

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

(
    -- Шаг 5: Выгрузка глобальных системных шаблонов меню
    SELECT 
        id AS template_id,
        'SYSTEM' AS scope, -- Хардкод-маркер происхождения для UI Flutter
        menu_title,
        created_at
    FROM master_menu_templates
)
UNION ALL
(
    -- Шаг 6: Выгрузка уникальных кастомных шаблонов конкретного предприятия
    SELECT 
        id AS template_id,
        'COMPANY' AS scope, -- Хардкод-маркер происхождения для UI Flutter
        menu_title,
        created_at
    FROM company_custom_menus
    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)

Возвращается при успешной выборке гибридного реестра. Отсутствие вложенного массива items_matrix гарантирует компактность payload и микросекундный парсинг JSON-пакета на мобильном устройстве.

{
  "home_group_id": "company-office-center",
  "total_count": 2,
  "limit": 20,
  "offset": 0,
  "templates": [
    {
      "template_id": 102,
      "scope": "COMPANY",
      "menu_title": "Пятничный Бургер-День v2",
      "created_at": "2026-07-01T23:28:00Z"
    },
    {
      "template_id": 1,
      "scope": "SYSTEM",
      "menu_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-MENU-FETCH-LIMIT-EXCEEDED",
  "message": "Превышен максимальный лимит размера страницы (максимум — 100 шаблонов меню за один запрос).",
  "details": {
    "requested_limit": 500,
    "allowed_max_limit": 100
  }
}

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

Выбрасывается, если клиент передал в Query-параметрах отрицательное значение смещения (offset=-20), нарушающее встроенную валидацию типов 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"
    }
  ]
}