Перейти к содержимому

Запись — контракт и переходы

Модуль registrations в poker-api: запись, отмена, чек-ин, walk-in, страйки. Все эндпоинты — за JwtAuthGuard.

Где код: repos/poker-api/apps/server/src/registrations/.

Метод и путь Кто может Что делает
POST /tournaments/{id}/register игрок за себя запись; отдаёт { status: 'registered' | 'waitlist' }
POST /tournaments/{id}/cancel-registration игрок за себя отмена; после дедлайна с занятого места — страйк
POST /tournaments/{id}/check-in owner, td отметить пришедшего
POST /tournaments/{id}/cancel-check-in owner, td снять отметку
POST /tournaments/{id}/walk-in owner, td посадить по телефону и имени
GET /tournaments/{id}/registrations owner, td список с именем, ником, аватаром и телефоном
GET /tournaments/{id}/strikes owner, td страйки турнира
POST /strikes/{id}/forgive owner, td простить страйк

Пути даны без префикса /api/v1.

Код Когда
409 REGISTRATION_CLOSED статус турнира не registration
403 REGISTRATION_BANNED у профиля игрока banUntil в будущем
409 ALREADY_REGISTERED живая запись уже есть
404 NOT_FOUND турнира или записи нет
409 INVALID_STATUS операция не подходит текущему статусу записи

Место и запись — одна транзакция под блокировкой строки турнира. SELECT … FOR UPDATE по tournaments сериализует конкурентные записи, поэтому подсчёт занятых мест точен и capacity не переполняется. Занятыми считаются registered и checked_in.

await tx.$queryRaw`SELECT id FROM tournaments WHERE id = ${tournamentId}::uuid FOR UPDATE`
const takenSeats = await tx.registration.count({
where: { tournamentId, status: { in: ['registered', 'checked_in'] } },
})
const seat = takenSeats < tournament.capacity ? 'registered' : 'waitlist'

Повторная запись после отмены переиспользует ту же строку и заново отсчитывает позицию в очереди: createdAt = now. Уникальный индекс по (tournamentId, userId) не даёт завести вторую строку; гонка на создании ловится по P2002 и превращается в 409 ALREADY_REGISTERED.

Освободившееся место сразу поднимает первого из листа ожидания guarded-write’ом, и пуш waitlist.promoted уходит только тем, кто реально поднялся, — не всей очереди.

Отмена чек-ина смотрит на seatedFromWaitlist. Если человека сажали из очереди, он возвращается в waitlist, иначе в registered. Без этого флага человек из листа ожидания получил бы при старте неявку и страйк за турнир, куда его не звали.

Страйки и бан — под блокировкой строки профиля. strikesToBan (2) непрощённых страйков за strikeWindowDays (30) дают banUntil = now + banDays (14). Прощение пересчитывает бан и снимает его, если активных страйков стало меньше порога.

  • Walk-in намеренно не проверяет capacity. Это решение ТД, а не баг; но значит, число записей может превысить заявленное число мест.
  • Walk-in идемпотентен: повтор и гонка возвращают ту же строку. Если телефон принадлежит существующему пользователю, запись привязывается к нему, иначе заводится GuestPlayer — он клеймится при первом входе владельца телефона.
  • lateRegLevel не работает. Поле в шаблоне блайндов есть, но нигде не проверяется: walk-in доступен на любом уровне, пока турнир running.
  • Пуш подписчикам followee.registered — best-effort. Сбой отправки не роняет запись, и это осознанно.
  • Отмена турнира гасит живые регистрации без страйков.