Skip to content

Repository files navigation

VOVO API

REST API маркетплейса VOVO. Тестовое задание — поиск товаров с фильтрацией, сортировкой и пагинацией.

Стек

  • PHP 8.4 + Laravel 11
  • MariaDB 11.4
  • Redis 7.4 — кеш, сессии, очереди, rate limiting
  • Nginx + PHP-FPM (Alpine)
  • spatie/laravel-query-builder — фильтры и сортировка
  • darkaonline/l5-swagger — документация API
  • larastan/larastan level 6 — статический анализ
  • laravel/pint — форматирование кода

Быстрый старт (Docker)

# 1. Клонировать репозиторий
git clone <repo-url> && cd vovo-api

# 2. Настроить окружение
cp .env.example .env

# 3. Поднять контейнеры и накатить миграции
make up
make migrate-fresh
Сервис URL
API http://localhost:8080/api/v1
Swagger http://localhost:8080/api/documentation
Health http://localhost:8080/api/health
Adminer http://localhost:8888

Adminer: сервер db, пользователь и пароль из .env

Команды

make help          # список всех команд
make up            # запустить контейнеры
make down          # остановить контейнеры
make build         # пересобрать образы
make logs          # логи всех контейнеров
make shell         # bash внутри PHP-контейнера

make migrate       # применить миграции
make migrate-fresh # пересоздать БД + залить сиды
make seed          # залить сиды

make test          # запустить тесты
make pint          # проверить code style
make stan          # статический анализ
make ci            # pint + stan + test (как в GitHub Actions)
make swagger       # сгенерировать Swagger-документацию

Эндпоинты

GET /api/health

Проверка состояния сервиса (БД + Redis).

{
  "message": "ok",
  "data": { "database": "ok", "cache": "ok" }
}

GET /api/v1/products

Поиск товаров с фильтрацией, сортировкой и пагинацией.

Rate limit: 60 запросов в минуту.

Заголовок X-Cache: HIT если результат из кеша, MISS при первом запросе. Кеш инвалидируется автоматически при изменении любого товара.

Параметры фильтрации

Параметр Тип Описание
q string Fulltext-поиск по названию
price_from numeric Цена от
price_to numeric Цена до
category_id integer ID категории
in_stock boolean В наличии (true / false)
rating_from numeric Рейтинг от (0–5)

Фильтры передаются как filter[q]=телефон, filter[price_from]=1000

Сортировка (sort)

price_asc · price_desc · rating_desc · newest

Пагинация

Параметр По умолчанию Максимум
page 1
per_page 15 500

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

GET /api/v1/products?filter[q]=телефон&filter[price_from]=1000&filter[in_stock]=true&sort=price_asc&page=1&per_page=10

Пример ответа

{
  "message": "Products retrieved successfully",
  "data": {
    "pagination": {
      "page": 1,
      "per_page": 10,
      "total": 3,
      "last_page": 1
    },
    "items": [
      {
        "id": 12,
        "name": "Телефон Xiaomi Redmi 13",
        "price": "12990.00",
        "category_id": 2,
        "category_name": "Электроника",
        "in_stock": true,
        "rating": 4.3,
        "created_at": "2026-04-21T10:00:00.000000Z",
        "updated_at": "2026-04-21T10:00:00.000000Z"
      }
    ]
  }
}

Архитектурные решения

Решение Почему
Fulltext вместо LIKE Использует индекс на MariaDB, быстрее на больших таблицах
Cache::tags(['products']) Групповая инвалидация — один flush() сбрасывает все закешированные страницы
ProductObserver Кеш сбрасывается автоматически при любом изменении модели
X-Cache заголовок Прозрачность для клиента и дебаггинга
DB::listen() Медленные запросы (>500ms) логируются в storage/logs/laravel.log
Redis для rate limiting Атомарные счётчики без race condition, общий для всех воркеров

Разработка без Docker

composer install
cp .env.example .env
# Настроить DB_* и REDIS_* в .env
php artisan key:generate
php artisan migrate --seed
php artisan serve
vendor/bin/phpunit          # тесты (SQLite in-memory)
vendor/bin/pint --test      # code style
vendor/bin/phpstan analyse  # статический анализ

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages