Skip to content

Repository files navigation

Promo Codes API

REST API для управления промокодами: создание, получение, обновление, удаление и активация по email.

Стек: NestJS 10, Prisma 5, PostgreSQL 16, class-validator, Jest + Supertest


Запуск

Требования

  • Docker + Docker Compose
  • Node.js 20+

Шаги

# 1. Запустить PostgreSQL
docker-compose up -d
# Автоматически создаёт две БД: promocodes (основная) и promocodes_test (тесты)# 2. Установить зависимости
npm install
# 3. Скопировать переменные окружения (значения по умолчанию работают с docker-compose)
cp .env.example .env
# 4. Применить миграции
npm run db:migrate
# 5. Запустить сервер
npm run start:dev # hot reload# или
npm run build && npm start

Сервер запустится на http://localhost:3000.

Переменные окружения

ПеременнаяОписаниеПо умолчанию
DATABASE_URLПодключение к основной БДpostgresql://postgres:postgres@localhost:5432/promocodes
DATABASE_URL_TESTПодключение к тестовой БДpostgresql://postgres:postgres@localhost:5432/promocodes_test
PORTПорт HTTP-сервера3000
NODE_ENVОкружениеdevelopment

Тесты

npm test# все e2e-тесты
npm run test:coverage # с покрытием

Формат ошибок

Все ошибки возвращаются в едином формате:

{
"error": {
"code": "PROMO_NOT_FOUND",
"message": "Promo code 'SUMMER20' does not exist."
}
}
КодHTTPКогда возникает
VALIDATION_ERROR400Невалидные или отсутствующие поля запроса
PROMO_NOT_FOUND404Промокод с таким кодом не найден
PROMO_DUPLICATE_CODE409Промокод с таким кодом уже существует
PROMO_ALREADY_ACTIVATED409Этот email уже активировал данный промокод
PROMO_EXPIRED422Срок действия промокода истёк
PROMO_LIMIT_REACHED422Достигнут лимит активаций
INTERNAL_ERROR500Непредвиденная ошибка сервера

API

POST /promo-codes — создать промокод

Тело запроса:

ПолеТипОбязательноеОграничения
codestringда1–64 символа, без пробелов. Сохраняется в верхнем регистре.
discount_percentintegerда1–100
activation_limitintegerда≥ 1
expires_atISO 8601нетДата истечения. Если не указана — промокод бессрочный.
curl -X POST http://localhost:3000/promo-codes \
-H 'Content-Type: application/json' \
-d '{"code":"SUMMER20","discount_percent":20,"activation_limit":100,"expires_at":"2026-12-31T23:59:59.000Z"}'

Ответ 201 Created:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"code": "SUMMER20",
"discount_percent": 20,
"activation_limit": 100,
"expires_at": "2026-12-31T23:59:59.000Z",
"created_at": "2026-04-01T10:00:00.000Z",
"updated_at": "2026-04-01T10:00:00.000Z"
}

Ошибки:VALIDATION_ERROR, PROMO_DUPLICATE_CODE


GET /promo-codes — список промокодов

Query-параметры:

ПараметрТипПо умолчаниюОграничения
limitinteger201–100
offsetinteger0≥ 0
curl 'http://localhost:3000/promo-codes?limit=10&offset=0'

Ответ 200 OK:

{
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"code": "SUMMER20",
"discount_percent": 20,
"activation_limit": 100,
"expires_at": "2026-12-31T23:59:59.000Z",
"created_at": "2026-04-01T10:00:00.000Z",
"updated_at": "2026-04-01T10:00:00.000Z",
"activation_count": 42
}
],
"total": 1,
"limit": 10,
"offset": 0
}

activation_count — текущее число выполненных активаций. Сортировка: по убыванию created_at.

Ошибки:VALIDATION_ERROR


GET /promo-codes/:code — получить промокод

curl http://localhost:3000/promo-codes/SUMMER20

Ответ 200 OK:

{
"id": "550e8400-e29b-41d4-a716-446655440000",
"code": "SUMMER20",
"discount_percent": 20,
"activation_limit": 100,
"expires_at": "2026-12-31T23:59:59.000Z",
"created_at": "2026-04-01T10:00:00.000Z",
"updated_at": "2026-04-01T10:00:00.000Z",
"activation_count": 42
}

Поиск регистронезависимый: summer20, Summer20 и SUMMER20 вернут одно и то же.

Ошибки:PROMO_NOT_FOUND


PATCH /promo-codes/:code — обновить промокод

Частичное обновление: передаются только изменяемые поля. Хотя бы одно поле обязательно.

Тело запроса:

ПолеТипОграничения
discount_percentinteger1–100
activation_limitinteger≥ 1
expires_atISO 8601 / nullnull — убрать срок действия (сделать промокод бессрочным)
curl -X PATCH http://localhost:3000/promo-codes/SUMMER20 \
-H 'Content-Type: application/json' \
-d '{"discount_percent":25,"expires_at":null}'

Ответ 200 OK: та же структура, что у GET /promo-codes/:code.

Ошибки:VALIDATION_ERROR, PROMO_NOT_FOUND


DELETE /promo-codes/:code — удалить промокод

curl -X DELETE http://localhost:3000/promo-codes/SUMMER20

Ответ 204 No Content: тело пустое.

Каскадно удаляет все связанные активации.

Ошибки:PROMO_NOT_FOUND


POST /promo-codes/:code/activate — активировать промокод

Каждый email может активировать один промокод только один раз. Активация атомарна: при одновременных запросах лимит не будет превышен.

Тело запроса:

ПолеТипОбязательноеОграничения
emailstringдаВалидный email. Нормализуется к нижнему регистру.
curl -X POST http://localhost:3000/promo-codes/SUMMER20/activate \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com"}'

Ответ 201 Created:

{
"activation_id": "660e8400-e29b-41d4-a716-446655440111",
"promo_code": "SUMMER20",
"email": "user@example.com",
"discount_percent": 20,
"activated_at": "2026-04-01T11:00:00.000Z"
}

Ошибки:VALIDATION_ERROR, PROMO_NOT_FOUND, PROMO_ALREADY_ACTIVATED, PROMO_EXPIRED, PROMO_LIMIT_REACHED

Приоритет ошибок (если одновременно выполняется несколько условий):

  1. PROMO_ALREADY_ACTIVATED — email уже активировал этот код
  2. PROMO_EXPIRED — срок действия истёк
  3. PROMO_LIMIT_REACHED — исчерпан лимит активаций

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages