Проект представляет собой систему аукционов, похожую на Telegram Gift Auction, с веб‑клиентом и сервером, в которой пользователи делают ставки (включая pre‑bid), соревнуются за подарки и получают результаты в реальном времени через Socket.IO.
- Сайт - https://aliph0thcryptobotauctions-gyewfjopkdv.netlify.app/
- Видео - https://youtu.be/0ywE7Q3Z8AU
- Postman - https://documenter.getpostman.com/view/20130039/2sBXVkAUL2
- Аукцион — объект, у которого есть подарок, количество раундов, длительность первого и последующих раундов, политика возвратов, настройки анти-снайпинга, количество подарков за раунд и прочее. Он оркестрируется полностью сервером (начало, новый раунд и тд).
- Раунд — временной интервал внутри аукциона, где выбираются N победителей по сумме и времени ставок.
- Подарок — приз, который выдается победителям. Всего подарков =
giftsPerRound * roundCount. - Ставка — запись о ставке пользователя в текущем раунде. Ставка может быть обычной или автоматически перенесенной в следующий раунд.
- Пользователь — участник аукциона с балансом, ставками и инвентарем подарков.
- Вещь - (item) — объект, представляющий подарок в инвентаре пользователя после выигрыша, имеет номер выигрыша и цену.
- До старта аукциона пользователи могут делать ставки. При этом такие ставки участвуют в первом раунде как полноценные.
- Защита от дублирующих запросов.
idempotencyKeyпривязан к пользователю. Это защищает от попыток получить чужой результат по чужому ключу.
Проект использует MongoDB транзакции для атомарных операций (ставки, выдача предметов, создание аукциона и т.д.). Для этого MongoDB должна работать в режиме replica set.
- В docker-compose Mongo запускается с
--replSet rs0. - Инициализация replica set выполняется отдельным сервисом
mongo-init. - Для авторизации в replica set требуется
keyFile. Он генерируется при старте контейнера скриптом scripts/mongo-generate-keyfile.sh и хранится в volumemongo_keyfile.
Если вы запускаете MongoDB вне Docker, убедитесь, что:
mongodстартует с--replSet rs0и--keyFile ...;- в конфиге сервера выставлен
MONGO_HOST(обычноlocalhostдля локального запуска иmongoдля Docker); - строка подключения содержит
replicaSet=rs0.
- Рейтинг ставок хранится в Redis Sorted Set для каждого раунда.
- Сортировка — по сумме ставок (по убыванию).
- Сумма ставок (в текущем раунде) определяет место пользователя в рейтинге (т.е пользователи могут добавлять деньги к текущей ставке).
- При равных суммах используется tie‑break по последней ставке в БД: более ранняя ставка имеет приоритет.
- Победители раунда — это топ‑N участников (N =
giftsPerRound). - !!!!! Если в раунде участвует меньше пользователей, чем будет выдано подарков, то несмотря на это раунд завершится и участники получат подарки, однако, номера "сгоревших" подарков не сохраняются. Т.е, если в раунде раздается 5 подарков, а участников всего 3, то они получат подарки с номерами 1-3, а номера 4 и 5 - сгорят (мое допущение такое). Демо с аукциона в тестовом окружении Telegram, где видно, что при отсутствии участников подарки все равно "выдаются": ДЕМО
Минимальная ставка динамическая и зависит от текущего распределения ставок:
- Это сумма ставки пользователя на позиции N, где
N = оставшиеся подаркипо всему аукциону. - Если в рейтинге нет пользователя на позиции N, минимальная ставка равна
startPrice. - Минимальная ставка обновляется:
- при новой ставке;
- при завершении раунда;
- при старте нового раунда;
- При вводе суммы ставка предварительно просчитывается на сервере, и возвращается потенциальное место в рейтинге.
- Если до конца раунда осталось меньше порога времени, раунд автоматически продлевается на указанное количество секунд.
- Действует на каждом раунде (кроме предварительных ставок), если не включено только для 1 раунда.
- Продление применяется только если пользователь изменил позицию в топ‑рейтинге (по умолчанию в топ 3).
- Количество продлений ограничено параметром
limit. - Может быть отключено.
Поддерживаются три режима (я посчитал, что хорошим решением будет расширить возможность настройки возвратов, помимо стандартной в Telegram):
no_refund— ставки не возвращаются.each_round— по завершению раунда все непобедившие ставки возвращаются.over_supply— (по умолчанию, как в Telegram) если ставок больше, чем оставшихся подарков, излишки возвращаются сразу (один пользователь вытеснил другого).
При политиках возврата (т.е не each_round), допускающих перенос, возможен bid carry‑over:
- ставки участников, которые не получили подарок, автоматически переносятся в следующий раунд;
!!! В качестве аутентификации используется Telegram Login Widget, поэтому нужно создать Telegram бота:
- Создать бота через BotFather и получить токен.
- В
/server/.envуказать полученныйBOT_TOKEN - В
/client/.envуказатьVITE_BOT_USERNAME(без @) - Telegram Login Widget требует указания домена, поэтому для локального тестирования нужно использовать VS Code Port Forwarding, local tunnel, ngrok etc.
- После проброса порта клиента (5173 по умолчанию) в BotFather нужно выполнить команду
/setdomain, выбрать бота и указать домен (без протокола и слэшей). Например,wrktftv5-5173.euw.devtunnels.ms. Возможно, придется немного подождать, пока телеграм применит изменения.
Запуск:
git clone https://github.com/Aliph0th/cb_aucs.gitcd cb_aucs- Заполнить переменные окружения в
.envв/server,/client(примеры в.env.example) - Переменные окружения в корне проекта должны совпадать по значению с
/server/.env(MONGO**, REDIS**) (для docker compose) docker compose up -d --build
По умолчанию сервер будет доступен по адресу http://localhost:3333, клиент - http://localhost:5173 (нужно пробросить порт, как описано выше).
Основные слои:
- HTTP API (Controllers) — входная точка для запросов (аутентификация, rate limit, ставки, аукционы, инвентарь).
- Services (бизнес‑логика) — сценарии аукциона: ставки, ранги, возвраты, переносы, анти‑снайпинг.
- WS Gateway — realtime‑уведомления для клиентов (изменения топа, новые раунды, возвраты и т.д.).
- Persistency — MongoDB для ставок/пользователей/аукционов и Redis для ранжирования и быстрых операций по топам.
Технологии:
- Typescript, NestJS + Mongoose — основная логика и хранение данных.
- Redis — быстрые рейтинги и операции по ранжированию (ZSet) и BullMQ.
- Socket.IO — realtime коммуникация.
Для фоновых задач и регулярного тика аукционов используется очередь Bull поверх Redis.
Ключевой повторяющийся job — AUCTION_TICK, который запускается каждую секунду и дергает оркестратор аукциона.
Важно:
- без Redis очередь не запустится, и аукционы не будут тикать/переходить по раундам;
- частота тика сейчас фиксирована на 1s, меняется в
AuctionModuleпри добавлении repeat-job.
В API включено ограничение запросов на уровне пользователя (по userID из запроса). Используется скользящее окно на Redis Sorted Set:
-
Для каждого пользователя и маршрута хранится набор временных меток запросов.
-
Перед добавлением нового запроса все записи старше окна удаляются.
-
GET /auctions— 10 запросов за 5 секунд. -
POST /auctions— 5 запросов за 60 секунд. -
GET /auctions/:id/info— 30 запросов за 10 секунд. -
POST /bids— 5 запросов за 2 секунды. -
POST /users/authenticate— 1 запрос за 5 секунд. -
GET /users/me— 15 запросов за 5 секунд. -
GET /users/inventory— 10 запросов за 5 секунд. -
POST /users/balance— 10 запросов за 5 секунд.
Эндпоинты без ограничений:
GET /giftsPOST /users/bots/init
- auction_created — создан новый аукцион.
- auctions_started — аукционы начались (переход к раунду 1).
- new_round — начался новый раунд.
- auction_finished — аукцион завершен.
- top_changed — изменился рейтинг топ‑победителей.
- round_time_extended — раунд продлен антиснайпингом.
- out_of_winners — пользователь вышел из победителей раунда.
- bid_refunded — ставка возвращена пользователю.
- min_bid_updated — изменена минимальная ставка.
auction— создание/запуск/завершение аукционов, инфо по аукциону.bid— размещение ставок, ранжирование, возвраты, переносы.round— управление раундами, выбор победителей.gateway— WebSocket‑события и комнаты.
Скрипт: scripts/k6-auctions.js
Тестируются:
- аукционы (
GET /auctions,GET /auctions/:id/info) - ставки (
POST /bids) - пользователи (
GET /users/me)
Не тестируются: health, gifts.
Запуск:
- Запустить через Docker (profile
loadtest):pnpm run loadtest- или напрямую:
docker compose --profile loadtest run --rm k6
Смену аккаунта на клиенте как таковую не делал. Для смены нужно удалить auth_token из localStorage и удалить сессию в Telegram, после перезагрузить страницу