Это backend-система аукционов цифровых товаров, вдохновленная механикой Telegram Gift Auctions. Я не пытался угадать или воспроизвести внутреннюю логику Telegram — она все равно не публична. Вместо этого я смотрел на поведение продукта, фиксировал допущения там, где информации нет, и строил систему так, как ее пришлось бы делать в реальном продакшене: с деньгами, конкуренцией и нагрузкой. Проект про аккуратную финансовую модель, устойчивость к race conditions и архитектуру, которую не стыдно поддерживать.
Это backend для многокругового аукциона цифровых товаров. Основной фокус — не UI и не «фичи ради фич», а корректная работа в условиях высокой конкуренции и большого числа ставок.
Что я делал осознанно:
- ✅ анализировал наблюдаемое поведение аукционов,
- ✅ явно фиксировал все допущения,
- ✅ проектировал систему так, чтобы ее можно было объяснить, протестировать и восстановить после сбоев.
- Многокруговые аукционы: ставки автоматически переносятся между раундами.
- Финансы через ledger: каждая операция записывается, ничего не «теряется».
- Гарантированная целостность: деньги не дублируются и не исчезают.
- Безопасная конкуренция: MongoDB transactions и Redis locks.
- Высокая нагрузка: сотни тысяч и миллионы ставок в раунд на одном сервере.
- Real-time обновления через WebSocket с throttling.
- Полное тестовое покрытие: unit, integration и нагрузочные тесты.
- Node.js 18+ с TypeScript (strict mode)
- NestJS
- MongoDB (replica set, транзакции)
- Redis (кеш и распределенные блокировки)
- Socket.IO
- Swagger / OpenAPI
- React
- WebSocket для live-обновлений
- Page Visibility API для снижения лишних запросов
- Docker Compose
- Jest
- Pino (логирование)
- Helmet
- Rate Limiting
- Swagger UI — интерактивная API документация (
/api/docs) SPEC.md— детальная спецификация механики аукционаASSUMPTIONS.md— все явные допущенияPERFORMANCE_OPTIMIZATION.md,PERFORMANCE_REPORT.md— оптимизации и масштабируемость
- 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
- Frontend: http://localhost:3001
- Backend API: http://localhost:3000
- Swagger: http://localhost:3000/api/docs
Подробности — в QUICKSTART.md
API использует JWT (JSON Web Tokens) для аутентификации. Большинство эндпоинтов требуют аутентификации.
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
}
}POST /auth/login
Content-Type: application/json
{
"username": "testuser",
"password": "securepassword123"
}Ответ: Аналогичен ответу регистрации (JWT токен + информация о пользователе)
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 соединения также требуют 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 в переменных окружения!
В большинстве 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 }
});Бизнес-логика не размазана по контроллерам. Она живет в domain-сервисах: AuctionService, BidService, BalanceService. Контроллеры делают ровно то, что должны: валидация, авторизация, вызов сервисов.
В итоге:
- сервисы легко тестировать,
- логику можно читать отдельно от HTTP,
- код не превращается в кашу по мере роста.
Одновременные ставки — основная боль аукционов. Здесь она решается комбинацией транзакций MongoDB и Redis-блокировок. Конфликты обрабатываются через retry с backoff.
Ключевые оптимизации:
- выбор победителя через .limit(), а не загрузку всех ставок;
- возвраты денег батчами по cursor pagination;
- кеширование dashboard-запросов;
- WebSocket-обновления батчами раз в 100 мс.
Никаких 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-разработчик думает о продукте:
- как обращаться с деньгами,
- как переживать сбои,
- как масштабироваться,
- как оставлять после себя систему, а не набор файлов.
- ✅ 100k-1M 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 аукциона с нормальной финансовой моделью, устойчивостью к конкуренции и понятной архитектурой. Не идеальный. Зато честный, проверяемый и пригодный для реального использования.