Skip to content

KOR1K1/CryptoBotContest

Repository files navigation

🏆 Backend Auction Challenge — production-grade auction system

Это backend-система аукционов цифровых товаров, вдохновленная механикой Telegram Gift Auctions. Я не пытался угадать или воспроизвести внутреннюю логику Telegram — она все равно не публична. Вместо этого я смотрел на поведение продукта, фиксировал допущения там, где информации нет, и строил систему так, как ее пришлось бы делать в реальном продакшене: с деньгами, конкуренцией и нагрузкой. Проект про аккуратную финансовую модель, устойчивость к race conditions и архитектуру, которую не стыдно поддерживать.

GitHub TypeScript NestJS 10.3.0 MongoDB Redis Socket.IO

Last Commit License MIT


Видео-демонстрация проекта:

https://youtu.be/bJ4Xs78BjAo

О проекте

Это backend для многокругового аукциона цифровых товаров. Основной фокус — не UI и не «фичи ради фич», а корректная работа в условиях высокой конкуренции и большого числа ставок.

Что я делал осознанно:

  • анализировал наблюдаемое поведение аукционов,
  • ✅ явно фиксировал все допущения,
  • проектировал систему так, чтобы ее можно было объяснить, протестировать и восстановить после сбоев.

Ключевые возможности

  • Многокруговые аукционы: ставки автоматически переносятся между раундами.
  • Финансы через ledger: каждая операция записывается, ничего не «теряется».
  • Гарантированная целостность: деньги не дублируются и не исчезают.
  • Безопасная конкуренция: MongoDB transactions и Redis locks.
  • Высокая нагрузка: сотни тысяч и миллионы ставок в раунд на одном сервере.
  • Real-time обновления через WebSocket с throttling.
  • Полное тестовое покрытие: unit, integration и нагрузочные тесты.

Технический стек

Backend

  • Node.js 18+ с TypeScript (strict mode)
  • NestJS
  • MongoDB (replica set, транзакции)
  • Redis (кеш и распределенные блокировки)
  • Socket.IO
  • Swagger / OpenAPI

Frontend

  • React
  • WebSocket для live-обновлений
  • Page Visibility API для снижения лишних запросов

Инфраструктура и инструменты

  • Docker Compose
  • Jest
  • Pino (логирование)
  • Helmet
  • Rate Limiting

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


Быстрый старт

Требования

  • Node.js >= 18.0.0
  • Docker Desktop (для автоматического запуска MongoDB и Redis)

Запуск (одна команда)

# Клонировать репозиторий
git clone https://github.com/KOR1K1/CryptoBotContest.git

cd CryptoBotContest

# Скопировать .env файл из .env.example
cp .env.example .env

# Windows PowerShell:
# Copy-Item .env.example .env

# При необходимости — отредактировать .env
# (порты, креды, ключи и т.д.)

# Установить зависимости
npm install

# Запустить всё (MongoDB, Redis, Backend, Frontend)
docker-compose up --build

После запуска:

Подробности — в QUICKSTART.md


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

API использует JWT (JSON Web Tokens) для аутентификации. Большинство эндпоинтов требуют аутентификации.

Регистрация и вход

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

POST /auth/register
Content-Type: application/json

{
  "username": "testuser",
  "password": "securepassword123",
  "email": "user@example.com",  # опционально
  "initialBalance": 10000        # опционально, начальный баланс
}

Ответ:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": "507f1f77bcf86cd799439011",
    "username": "testuser",
    "email": "user@example.com",
    "balance": 10000,
    "lockedBalance": 0
  }
}

2. Вход

POST /auth/login
Content-Type: application/json

{
  "username": "testuser",
  "password": "securepassword123"
}

Ответ: Аналогичен ответу регистрации (JWT токен + информация о пользователе)

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

GET /auth/me
Authorization: Bearer <your-jwt-token>

Ответ:

{
  "id": "507f1f77bcf86cd799439011",
  "username": "testuser",
  "email": "user@example.com",
  "balance": 10000,
  "lockedBalance": 0,
  "createdAt": "2024-01-01T00:00:00.000Z"
}

Использование токена

После получения токена, включите его в заголовок Authorization для всех защищенных эндпоинтов:

Authorization: Bearer <your-jwt-token>

Пример: создание аукциона

POST /auctions
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "giftId": "507f1f77bcf86cd799439011",
  "totalGifts": 10,
  "totalRounds": 3,
  "roundDurationMs": 60000,
  "minBid": 100
}

Пример: размещение ставки

POST /auctions/507f1f77bcf86cd799439011/bids
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json

{
  "amount": 500
}

Защищенные эндпоинты

Эндпоинты, требующие аутентификации (🔒):

  • POST /auctions - создание аукциона
  • POST /auctions/:id/start - запуск аукциона (только создатель)
  • POST /auctions/:id/bids - размещение ставки
  • POST /gifts - создание подарка
  • PUT /gifts/:id - обновление подарка
  • DELETE /gifts/:id - удаление подарка
  • GET /users/:id - получение информации о пользователе
  • GET /users/:id/balance - получение баланса пользователя
  • GET /users/:id/bids - получение ставок пользователя
  • GET /auth/me - получение текущего пользователя

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

WebSocket соединения также требуют JWT токен:

import { io } from 'socket.io-client';

const socket = io('http://localhost:3000', {
  auth: {
    token: 'your-jwt-token'
  }
});

Срок действия токена

Токены действительны в течение 24 часов (настраивается через переменную окружения JWT_EXPIRES_IN).

Безопасность

  • Пароли хешируются с использованием bcrypt (10 раундов)
  • JWT токены подписываются секретным ключом (настраивается через JWT_SECRET)
  • Защита от brute-force через rate limiting
  • CORS настроен для безопасности

Важно: В production обязательно установите сильный JWT_SECRET в переменных окружения!


Что здесь сделано иначе (и почему)

1. Финансы через ledger

В большинстве pet-проектов баланс просто уменьшают числом. Это удобно, но бесполезно, если что-то пошло не так.

Здесь каждая операция — отдельная запись в immutable ledger. Баланс можно восстановить из истории, а инварианты проверяются явно: balance >= 0, lockedBalance >= 0, balance + lockedBalance = const.

Это медленнее на бумаге, но именно так системы переживают баги и инциденты.

// Вместо просто: user.balance -= amount
// Делаем:
await LedgerEntry.create({
  userId,
  type: 'LOCK',
  amount,
  referenceId: bid._id
});
await User.updateOne({ _id: userId }, {
  $inc: { balance: -amount, lockedBalance: +amount }
});

2. Архитектура

Бизнес-логика не размазана по контроллерам. Она живет в domain-сервисах: AuctionService, BidService, BalanceService. Контроллеры делают ровно то, что должны: валидация, авторизация, вызов сервисов.

В итоге:

  • сервисы легко тестировать,
  • логику можно читать отдельно от HTTP,
  • код не превращается в кашу по мере роста.

3. Конкурентность и нагрузка

Одновременные ставки — основная боль аукционов. Здесь она решается комбинацией транзакций MongoDB и Redis-блокировок. Конфликты обрабатываются через retry с backoff.

Ключевые оптимизации:

  • выбор победителя через .limit(), а не загрузку всех ставок;
  • возвраты денег батчами по cursor pagination;
  • кеширование dashboard-запросов;
  • WebSocket-обновления батчами раз в 100 мс.

4. Механика раундов

Никаких setTimeout. Состояние раунда хранится в базе, переходы делает scheduler. Сервис можно перезапустить в любой момент — он продолжит с того же места.

Поздние ставки не продлевают раунд, а переходят в следующий. Это убирает «преимущество последней секунды» и делает механику предсказуемой.

Производительность (single server):

  • 10k–100k ставок: сотни миллисекунд на выбор победителя.
  • 100k–1M: до полусекунды.
  • 1M–10M: работает, но уже требует мониторинга.

Все цифры получены на реальных нагрузочных тестах. Подробности — в PERFORMANCE_REPORT.md

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

Покрыты:

  • финансовые инварианты,
  • конкурентные ставки,
  • идемпотентность,
  • корректность возвратов,
  • детерминированность выбора победителя.

Тесты — не «для галочки», а для проверки самых неприятных сценариев.

Ключевые тесты

# Запуск всех тестов
npm test

# С покрытием
npm run test:cov

# Watch mode
npm run test:watch

Критические сценарии проверены:

  • ✅ Финансовая целостность (инварианты баланса)
  • ✅ Конкурентные ставки (race conditions)
  • ✅ Идемпотентность операций (безопасные retry)
  • ✅ Winner selection (детерминированность)
  • ✅ Refund correctness (батчинг, проверки статуса)

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

src/
├── models/              # Mongoose schemas (User, Auction, Bid, LedgerEntry)
├── services/            # Domain services
│   ├── auction/        # AuctionService — core business logic
│   ├── bid/            # BidService — bid management
│   ├── balance/        # BalanceService — financial operations
│   ├── scheduler/      # RoundSchedulerService — round transitions
│   ├── throttler/      # BidUpdateThrottlerService — WebSocket optimization
│   └── redis-lock/     # RedisLockService — distributed locks
├── controllers/         # API endpoints (только валидация + вызов сервисов)
├── gateways/           # WebSocket gateway (real-time updates)
├── config/             # Configuration
└── integration/        # Integration tests

docs/
├── SPEC.md             # 📄 Детальная спецификация механики
├── ASSUMPTIONS.md      # 📝 Все явные допущения
└── PERFORMANCE_*.md    # 📊 Оптимизации производительности

Безопасность

  • Helmet и CORS
  • rate limiting на нескольких уровнях
  • строгая валидация DTO
  • базовая аутентификация WebSocket
  • транзакции MongoDB как защита от race conditions

Использование ИИ

ИИ использовался осознанно: для ускорения генерации кода и поиска документации. Все решения принимались вручную, каждая часть логики проверялась, все допущения зафиксированы в документации.


Почему этот проект вообще имеет смысл

Этот репозиторий не про «идеальное соответствие ТЗ». Он про то, как backend-разработчик думает о продукте:

  • как обращаться с деньгами,
  • как переживать сбои,
  • как масштабироваться,
  • как оставлять после себя систему, а не набор файлов.

🚀 Roadmap (масштабируемость)

Single Server (реализовано)

  • ✅ 100k-1M bids/round с оптимизациями

Large Scale (1M-10M bids/round) — требует горизонтального масштабирования

  • Horizontal scaling (multiple API instances + load balancer)
  • Socket.IO Redis adapter (multi-server WebSocket)
  • MongoDB replica set с read replicas
  • Message queue для асинхронной обработки ставок

Подробнее: SINGLE_SERVER_OPTIMIZATIONS.md


Итог

Это production-grade backend аукциона с нормальной финансовой моделью, устойчивостью к конкуренции и понятной архитектурой. Не идеальный. Зато честный, проверяемый и пригодный для реального использования.

About

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors