← Все проекты уровня 6
Уровень 6 · Первые веб-сервисыВариант C

Каталог рецептов

Редактируемый каталог рецептов с измеримым количеством порций, ингредиентами и упорядоченными шагами.

Техническое задание

Модель данных и назначение

Реализуйте на Go REST API каталога рецептов без внешней базы: записи живут в памяти и исчезают после перезапуска. Рецепт имеет id, title, portions, ingredients, steps, createdAt и updatedAt. portions — положительное целое число от 1 до 100. Каждый ingredient содержит name и amount; amount — положительное конечное десятичное число, unit — короткая строка единицы измерения. Шаги представляются упорядоченным непустым списком строк и возвращаются в исходном порядке. id и временные метки назначаются сервером.

API, проверки и эксплуатация

POST /api/recipes создаёт запись и отвечает 201. title после trim содержит от 1 до 160 символов. Список ingredients содержит 1–100 элементов; имя каждого ингредиента непустое и не длиннее 80 символов, amount больше нуля, unit содержит 1–24 символа. Повторение ингредиента с одинаковым нормализованным именем в одном рецепте запрещено с 400; регистр и крайние пробелы при этой проверке игнорируются. steps содержит 1–50 непустых строк длиной до 1000 символов каждая. Неизвестные поля, нулевое число порций, NaN/Infinity в количествах и неверные типы отклоняются с 400 и кодом ошибки.

GET /api/recipes поддерживает limit (по умолчанию 20, диапазон 1–100), offset (неотрицательный, по умолчанию 0) и необязательный ingredient. Фильтр совпадает с именем ингредиента без учёта регистра и крайних пробелов. Результаты сортируются по title без учёта регистра, затем по id. Ответ включает items и метаданные limit, offset, total. GET /api/recipes/{id} возвращает запись или 404. PUT /api/recipes/{id} полностью заменяет редактируемые поля после всех тех же проверок, сохраняя id и createdAt и устанавливая новый updatedAt. DELETE возвращает 204 либо 404. Все ошибки имеют единый JSON-формат; неизвестный маршрут — 404, неподдерживаемый метод — 405. Ограничьте тело запроса, задайте конечные HTTP-таймауты и не продолжайте обработку после отмены контекста. Параллельный GET не должен видеть частично заменённый список ингредиентов или шагов: запись целиком проверяется до атомарной замены под синхронизацией. Сервис не логирует содержимое рецептов и не использует внешние провайдеры. Пустая страница при offset больше total возвращает пустой items и исходный total. Для фильтра ингредиента пробелы по краям удаляются до проверки, а пустое после нормализации значение возвращает 400.

Структура программы

Небольшой internal/httpapi содержит обработчики и DTO, internal/recipes — валидацию, замену рецепта и фильтр ингредиентов, internal/recipememory — хранилище в памяти. main.go читает конфигурацию, связывает пакеты, запускает сервер и выполняет shutdown; правила тестируются без HTTP.

Критерии готовности

Ожидаемый результат

Поставка

  • Запускаемый Go REST API ведёт каталог рецептов в памяти и предоставляет CRUD, фильтр ингредиента и пагинацию.
  • README описывает ограничения частей рецепта, формат JSON и статусы ошибок; офлайн-тесты используют временный HTTP-сервер и фиксированные часы.

Приёмка

  • Валидный рецепт создаётся с кодом 201; шаги и ингредиенты после чтения сохраняют исходный порядок.
  • Portions=0, amount=0, пустой шаг и дубли после нормализации дают 400 без частичной записи.
  • Фильтр ingredient без учёта регистра возвращает только совпадения и вычисляет total до применения offset.
  • PUT заменяет все редактируемые поля, сохраняя id и createdAt; отсутствующий id возвращает 404.
  • Одновременный GET и PUT видят целую старую или новую запись, но не частично заменённые списки.
  • README показывает зависимости между HTTP, рецептами и памятью; валидация рецептов проверяется без HTTP.