Skip to content

Repository files navigation

Recipe Service API

REST API сервис для пользователей, рецептов, ингредиентов, избранных рецептов и расширенного MongoDB-контента рецептов.

Проект реализован на C++ с использованием userver, PostgreSQL и MongoDB. API документирован через OpenAPI, для локального запуска подготовлены Dockerfile и docker-compose.yaml.

Возможности

  • Регистрация пользователя.
  • Логин и получение JWT-токена.
  • Получение текущего пользователя по JWT.
  • Поиск пользователя по логину.
  • Поиск пользователей по имени или фамилии.
  • Создание рецепта.
  • Получение и поиск рецептов.
  • Добавление ингредиента в рецепт.
  • Получение ингредиентов рецепта.
  • Получение рецептов конкретного пользователя.
  • Добавление рецепта в избранное.
  • Хранение расширенного описания рецептов, комментариев и activity logs в MongoDB.
  • Запись события активности рецепта через API.
  • Получение полного рецепта из PostgreSQL + MongoDB.
  • Создание и обновление MongoDB details рецепта.
  • Добавление и получение комментариев рецепта.

Стек

  • C++.
  • userver.
  • PostgreSQL 15.
  • MongoDB 7.
  • Docker Compose.
  • OpenAPI 3.0.
  • Python unittest (smoke-тесты API)
  • Structurizr

Структура проекта

.
├── config/
|   └── config.yaml
├── docs/
|   └── openapi.yaml
├── mongo/
|   ├── validation.js
|   ├── data.js
|   └── queries.js
├── sql/
|   ├── schema.sql
|   ├── data.sql
|   └── queries.sql
├── src/
|   ├── auth/
|   ├── v1/
|   └── main.cpp
├── test/
|   ├── README.md
|   ├── test_api.py
|   └── test_mongo.py
├── CMakeLists.txt
├── Dockerfile
├── docker-compose.yaml
├── requirements.txt
├── optimization.md
├── schema_design.md
└── README.md

Архитектура

Диаграмма контекста системы (C1)

System Context

Диаграмма показывает пользователей системы, внешние взаимодействия и основные контейнеры приложения.

Диаграмма контейнеров (C2)

Container Diagram

Диаграмма контейнеров показывает взаимодействие между Recipe API, PostgreSQL, MongoDB и Swagger UI.

Диаграмма компонентов (C3)

Component Diagram

Диаграмма компонентов показывает внутреннюю структуру Recipe API: модули аутентификации, рецептов, ингредиентов, избранного и MongoDB-контента.

SQL-файлы

Файл Назначение
sql/schema.sql Создание расширения pg_trgm, таблиц, PK, FK, constraints и индексов.
sql/data.sql Тестовые данные
sql/queries.sql SQL-запросы для всех операций варианта задания
optimization.md Обоснование индексов, частые запросы, примеры EXPLAIN ANALYZE
schema_design.md Документная модель MongoDB и обоснование embedded/references
mongo/validation.js Создание коллекций MongoDB, $jsonSchema-валидация и индексы
mongo/data.js Тестовые данные MongoDB, минимум 10 документов в каждой коллекции
mongo/queries.js CRUD-запросы MongoDB и aggregation pipelines
docs/openapi.yaml OpenAPI 3.0 спецификация REST API.

В docker-compose.yaml файлы schema.sql и data.sql монтируются в /docker-entrypoint-initdb.d с числовыми префиксами, чтобы PostgreSQL сначала создал схему, а затем загрузил данные. queries.sql - файл с запросами.

MongoDB при первом запуске применяет mongo/validation.js, затем mongo/data.js. Скрипт mongo/queries.js можно выполнять вручную через mongosh.

Схема БД

users

Пользователи сервиса.

Колонка Тип Ограничения
id SERIAL PRIMARY KEY
username VARCHAR(100) NOT NULL, UNIQUE, не пустая строка
password_hash VARCHAR(255) NOT NULL
first_name VARCHAR(100) NOT NULL, не пустая строка
last_name VARCHAR(100) NOT NULL, не пустая строка
created_at TIMESTAMP NOT NULL, DEFAULT CURRENT_TIMESTAMP

ingredients

Справочник ингредиентов.

Колонка Тип Ограничения
id SERIAL PRIMARY KEY
name VARCHAR(200) NOT NULL, UNIQUE, не пустая строка
unit VARCHAR(50) NOT NULL, не пустая строка
created_at TIMESTAMP NOT NULL, DEFAULT CURRENT_TIMESTAMP

recipes

Рецепты пользователей.

Колонка Тип Ограничения
id SERIAL PRIMARY KEY
owner_id INTEGER NOT NULL, FK -> users(id) ON DELETE CASCADE
name VARCHAR(255) NOT NULL, не пустая строка
created_at TIMESTAMP NOT NULL, DEFAULT CURRENT_TIMESTAMP
updated_at TIMESTAMP NOT NULL, DEFAULT CURRENT_TIMESTAMP

recipe_ingredients

Связь многие-ко-многим между рецептами и ингредиентами.

Колонка Тип Ограничения
id SERIAL PRIMARY KEY
recipe_id INTEGER NOT NULL, FK -> recipes(id) ON DELETE CASCADE
ingredient_id INTEGER NOT NULL, FK -> ingredients(id)
quantity DECIMAL(10, 2) NOT NULL, DEFAULT 0, CHECK (quantity >= 0)
created_at TIMESTAMP NOT NULL, DEFAULT CURRENT_TIMESTAMP

Дополнительно: UNIQUE(recipe_id, ingredient_id).

user_favorites

Избранные рецепты пользователей.

Колонка Тип Ограничения
id SERIAL PRIMARY KEY
user_id INTEGER NOT NULL, FK -> users(id) ON DELETE CASCADE
recipe_id INTEGER NOT NULL, FK -> recipes(id) ON DELETE CASCADE
created_at TIMESTAMP NOT NULL, DEFAULT CURRENT_TIMESTAMP

Дополнительно: UNIQUE(user_id, recipe_id).

Индексы

Индекс Назначение
idx_users_first_name_trgm Поиск пользователей по маске имени.
idx_users_last_name_trgm Поиск пользователей по маске фамилии.
idx_ingredients_name_trgm Поиск ингредиентов по части названия.
idx_recipes_owner_id Получение рецептов пользователя и поддержка FK.
idx_recipes_name_trgm Поиск рецептов по маске названия.
idx_recipe_ingredients_recipe_id Получение ингредиентов рецепта.
idx_recipe_ingredients_ingredient_id JOIN по ингредиентам и поддержка FK.
idx_user_favorites_user_id Выборки избранного по пользователю и поддержка FK.
idx_user_favorites_recipe_id Выборки избранного по рецепту и поддержка FK.

Для ILIKE '%text%' используется pg_trgm и GIN-индексы, потому что обычные btree-индексы плохо подходят для поиска по подстроке.

Запуск PostgreSQL и API

docker compose up --build

После запуска доступны:

  • API: http://localhost:8080
  • Swagger UI: http://localhost:8081
  • PostgreSQL: localhost:5432
  • MongoDB: localhost:27017

Остановить сервисы:

docker compose down

При первом запуске PostgreSQL автоматически применяет:

  1. sql/schema.sql
  2. sql/data.sql

MongoDB автоматически применяет:

  1. mongo/validation.js
  2. mongo/data.js

Если нужно пересоздать БД с нуля:

docker compose down
docker compose up --build

Ручное применение SQL

Если PostgreSQL уже запущен, можно применить схему и данные вручную.

PowerShell:

Get-Content sql\schema.sql | docker compose exec -T postgres psql -U app -d recipe_app_db
Get-Content sql\data.sql | docker compose exec -T postgres psql -U app -d recipe_app_db

Проверить количество записей:

docker compose exec -T postgres psql -U app -d recipe_app_db -c "SELECT 'users' AS table_name, count(*) FROM users UNION ALL SELECT 'ingredients', count(*) FROM ingredients UNION ALL SELECT 'recipes', count(*) FROM recipes UNION ALL SELECT 'recipe_ingredients', count(*) FROM recipe_ingredients UNION ALL SELECT 'user_favorites', count(*) FROM user_favorites;"

SQL-запросы операций

Все запросы для варианта задания находятся в sql/queries.sql:

  • создание пользователя;
  • поиск пользователя по логину;
  • поиск пользователей по маске имени или фамилии;
  • создание рецепта;
  • получение списка рецептов;
  • поиск рецептов по названию;
  • добавление ингредиента в рецепт;
  • получение ингредиентов рецепта;
  • получение рецептов пользователя;
  • добавление рецепта в избранное.

MongoDB

MongoDB используется для документной части рецептов:

  • recipe_details - расширенное описание рецепта, шаги, фото, теги, нутриенты и заметки;
  • comments - комментарии и оценки пользователей;
  • recipe_activity_logs - события просмотра, комментирования, обновления и добавления в избранное.

Запустить MongoDB вместе с API:

docker compose up --build

Открыть shell:

docker compose exec mongo mongosh recipe_app_mongo

Проверить количество документов:

docker compose exec -T mongo mongosh recipe_app_mongo --quiet --eval "db.recipe_details.countDocuments(); db.comments.countDocuments(); db.recipe_activity_logs.countDocuments();"

Выполнить примеры CRUD и aggregation:

docker compose exec -T mongo mongosh recipe_app_mongo < mongo/queries.js

PowerShell:

Get-Content mongo\queries.js -Encoding UTF8 | docker compose exec -T mongo mongosh recipe_app_mongo

Описание выбора embedded documents и references находится в schema_design.md.

Оптимизация

Описание оптимизаций находится в optimization.md.

В документе описаны:

  • назначение каждого индекса;
  • частые запросы приложения;
  • примеры планов EXPLAIN ANALYZE;
  • сравнение поведения до и после добавления индексов;
  • вывод по партиционированию.

Партиционирование

Партиционирование для текущей версии сервиса не требуется. В системе нет явно большой временной таблицы вроде журнала событий, заказов или истории просмотров.

Если в будущем появится крупная таблица событий, например recipe_views или recipe_history, ее можно будет партиционировать по created_at помесячно.

API endpoints

Метод URL Описание Auth
POST /v1/register Регистрация пользователя Нет
POST /v1/login Логин и получение JWT-токена Нет
GET /v1/me Получение текущего пользователя Да
GET /v1/users/by-login/{username} Поиск пользователя по логину Нет
GET /v1/users/search?mask={mask} Поиск пользователей по имени или фамилии Нет
GET /v1/recipes Получение списка рецептов Нет
POST /v1/recipes Создание рецепта Да
GET /v1/recipes/search?name={name} Поиск рецептов по названию Нет
GET /v1/recipes/{recipe_id} Получение рецепта с MongoDB details Нет
PUT /v1/recipes/{recipe_id}/details Создание или замена MongoDB details Да
GET /v1/recipes/{recipe_id}/comments Получение комментариев рецепта из MongoDB Нет
POST /v1/recipes/{recipe_id}/comments Добавление комментария в MongoDB Да
GET /v1/recipes/{recipe_id}/ingredients Получение ингредиентов рецепта Нет
POST /v1/recipes/{recipe_id}/ingredients Добавление ингредиента в рецепт Да
GET /v1/users/{user_id}/recipes Получение рецептов пользователя Нет
POST /v1/users/{user_id}/favorites Добавление рецепта в избранное Да
POST /v1/recipes/{recipe_id}/activity-logs Запись события активности в MongoDB Нет

Полное описание схем запросов, ответов, статус-кодов и примеров находится в docs/openapi.yaml.

Dynamic Diagram — Registration

Registration Dynamic

Сценарий регистрации пользователя.

Dynamic Diagram — Login

Login Dynamic

Сценарий аутентификации пользователя и выдачи JWT.

Dynamic Diagram — Create Recipe

Create Recipe Dynamic

Сценарий создания нового рецепта авторизованным пользователем.

Аутентификация

Для аутентификации используется JWT-токен.

  1. Пользователь регистрируется через POST /v1/register.
  2. Пользователь логинится через POST /v1/login.
  3. Сервер возвращает access_token.
  4. Для защищенных endpoint токен передается в заголовке:
Authorization: Bearer <access_token>

JWT проверяется в middleware для следующих endpoints:

  • GET /v1/me
  • POST /v1/recipes
  • PUT /v1/recipes/{recipe_id}/details
  • POST /v1/recipes/{recipe_id}/comments
  • POST /v1/recipes/{recipe_id}/ingredients
  • POST /v1/users/{user_id}/favorites

Для защищенных операций сервис не только проверяет JWT, но и сверяет владельца ресурса с пользователем из токена. Если owner_id или user_id передан в запросе и не совпадает с JWT, API возвращает 403 Forbidden.

Запуск через Docker Compose

docker compose up --build

После запуска доступны:

  • API: http://localhost:8080
  • Swagger UI: http://localhost:8081
  • PostgreSQL: localhost:5432

Остановить сервисы:

docker compose down

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

Регистрация

Обязательные поля: username, password, first_name, last_name.

curl -X POST http://localhost:8080/v1/register \
  -H "Content-Type: application/json" \
  -d '{"username":"user","password":"secret123","first_name":"user","last_name":"name"}'

Логин

curl -X POST http://localhost:8080/v1/login \
  -H "Content-Type: application/json" \
  -d '{"username":"user","password":"secret123"}'

Получение текущего пользователя

curl http://localhost:8080/v1/me \
  -H "Authorization: Bearer <access_token>"

Поиск пользователя по логину

curl http://localhost:8080/v1/users/by-login/user

Поиск пользователей

curl "http://localhost:8080/v1/users/search?mask=us"

Создание рецепта

curl -X POST http://localhost:8080/v1/recipes \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{"name":"My recipe"}'

owner_id можно не передавать: сервис берет владельца из JWT. Если передать owner_id, он должен совпадать с пользователем из токена.

Получение списка рецептов

curl http://localhost:8080/v1/recipes

Поиск рецептов

curl "http://localhost:8080/v1/recipes/search?name=Pasta"

Получение рецепта с MongoDB details

curl http://localhost:8080/v1/recipes/1

Если рецепт есть в PostgreSQL, но для него еще нет документа в recipe_details, API возвращает 200 и поле details: null.

Создание или обновление MongoDB details

curl -X PUT http://localhost:8080/v1/recipes/1/details \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{"title":"Pasta Bolognese","description":"Домашняя паста с мясным соусом","difficulty":"medium","cooking_time_minutes":45,"servings":4,"tags":["pasta","dinner"],"steps":[{"order":1,"title":"Подготовить овощи","text":"Нарезать лук и чеснок","duration_minutes":10}]}'

Добавление комментария

curl -X POST http://localhost:8080/v1/recipes/1/comments \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{"comment":"Очень вкусно","rate":5}'

user_id для комментария также берется из JWT. Явно переданный user_id должен совпадать с пользователем из токена.

Получение комментариев рецепта

curl http://localhost:8080/v1/recipes/1/comments

Добавление ингредиента

Обязательные поля: name, unit. Поле quantity опционально, по умолчанию используется 0.

curl -X POST http://localhost:8080/v1/recipes/1/ingredients \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{"name":"sugar","unit":"g","quantity":120}'

Получение ингредиентов рецепта

curl http://localhost:8080/v1/recipes/1/ingredients

Получение рецептов пользователя

curl http://localhost:8080/v1/users/1/recipes

Если пользователя нет, endpoint возвращает 404 и тело user_not_found.

Добавление рецепта в избранное

curl -X POST http://localhost:8080/v1/users/1/favorites \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer <access_token>" \
  -d '{"recipe_id":5}'

user_id в URL должен совпадать с пользователем из JWT. Первое добавление возвращает 201, повторное добавление того же рецепта в избранное возвращает 200 и status: already_exists.

Запись события активности рецепта в MongoDB

curl -X POST http://localhost:8080/v1/recipes/1/activity-logs \
  -H "Content-Type: application/json" \
  -d '{"user_id":5,"action":"view","metadata":{"source":"web","ip":"127.0.0.1","user_agent":"Mozilla/5.0"}}'

Тестирование

Сначала нужно запустить сервис:

docker compose up --build

Затем выполнить smoke-тесты:

python -m unittest discover -s test -p "test_*.py" -v

По умолчанию тесты обращаются к http://localhost:8080. При необходимости адрес можно изменить:

$env:API_BASE_URL = "http://localhost:8080"
python -m unittest discover -s test -p "test_*.py" -v

Документация API

OpenAPI-спецификация находится по явному пути docs/openapi.yaml. Описание MongoDB-модели находится в schema_design.md, оптимизации PostgreSQL - в optimization.md, инструкции по тестам - в test/README.md.

Swagger UI поднимается вместе с Docker Compose и доступен по адресу:

http://localhost:8081

Просмотр C4-диаграмм через Structurizr

Для локального просмотра и редактирования диаграмм используется Structurizr.

Запуск:

docker run -it --rm -p 8088:8080 `
-v ${PWD}:/usr/local/structurizr `
structurizr/structurizr local

После запуска интерфейс будет доступен по адресу:

http://localhost:8088

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages