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

Источник: https://docs.novapoker.ru/dev/registration/
Дорожка: разработка
Сверено с кодом: Mon Aug 24
Код: repos/poker-api/apps/server/src/registrations/registrations.controller.ts, repos/poker-api/apps/server/src/registrations/registrations.service.ts

---

Модуль `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`.

```ts
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.** Сбой отправки не
  роняет запись, и это осознанно.
- **Отмена турнира гасит живые регистрации без страйков.**

## Что рядом

- [Запись и лист ожидания](https://docs.novapoker.ru/product/registration.md) — те же правила словами продукта.
- [День игры: чек-ин, walk-in, санкции](https://docs.novapoker.ru/dev/game-day.md) — что происходит дальше.
- [poker-api](https://docs.novapoker.ru/dev/poker-api.md) — как запустить сервис локально.
