# Клубы — контракт и оргконтекст

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

---

Клуб в коде — `Organizer`. Всё, что делает клуб, проходит проверку
`OrganizersService.requireRole`, поэтому раздел стоит читать вместе с
[аутентификацией и ролями](https://docs.novapoker.ru/dev/auth.md).

## Оргконтекст на фронте

Поверх обычной авторизации приложение организатора держит **оргконтекст**: хук
`useOrg` тянет членства из `GET /organizers/mine`.

- одно членство — выбирается автоматически;
- несколько — показывается экран `select-org`, клуб можно сменить в любой момент.

Роль внутри клуба определяет и набор вкладок, и доступные действия. Вкладка
«Клуб» существует только у владельца, у дилера «Турниры» называется «Назначения».

## Контракт

| Метод и путь | Кто может |
|---|---|
| `GET /organizers/mine` | любой вошедший — свои членства |
| `GET`/`PATCH /organizers/{id}` | `owner` |
| `GET`/`POST /organizers/{id}/members` | `owner` |
| `DELETE /organizers/{id}/members/{memberId}` | `owner` |
| `GET`/`POST /organizers/{id}/dealer-invites` | `owner` |
| `POST /dealer-invites/claim` | приглашённый дилер |
| `POST /organizers/{id}/locations`, `GET`/`PATCH /locations/{id}`, `GET /locations` | `owner`; каталог залов публичный |
| `GET`/`POST /organizers/{id}/blind-templates` | `owner` |
| `POST /organizers/{id}/tournaments` | `owner`, `td` |
| `POST /tournaments/{id}/dealers`, `PATCH /tournaments/{id}/dealers/{dealerUserId}` | `owner` |
| `GET /me/dealer-assignments` | дилер — свои назначения |
| `GET /dealers`, `GET /dealers/{userId}`, `PATCH /dealers/me` | каталог публичный, правка своя |

Пути даны без префикса `/api/v1`. Заведение самого клуба — операция платформы:
`POST /admin/organizers`, право `organizers.manage`.

## Приглашение дилера

`DealerInvite` — телефон плюс одноразовый код с TTL. Особенность, которая
удивляет: **платформа этот код не доставляет**. Клуб получает его в ответе и
показывает на экране, дальше передаёт человеку сам.

Код срабатывает **только с того номера**, на который выписан: `claim` сверяет
телефон вошедшего с телефоном приглашения.

Канал доставки одноразовых кодов у платформы есть — тот же, что для входа, — но
приглашение через него не идёт. Это осознанное состояние, а не забытая ветка.

## Залы и структуры блайндов

`Location` — зал: имя, город из справочника, адрес, таймзона, координаты,
описание, фото. Каталог залов читается публично, правит владелец.

`BlindTemplate` — уровни в JSON, стартовый стек, `lateRegLevel`. Значение
`organizerId = null` означает **платформенный шаблон**, доступный всем клубам;
сид кладёт один такой — «Стандарт 20 мин».

**`lateRegLevel` нигде не проверяется.** Поле хранится и отдаётся, но ни одна
ветка кода на него не смотрит: walk-in доступен на любом уровне, пока турнир
`running`.

## Назначение дилера на стол

`POST /tournaments/{id}/dealers` создаёт строку в `tournament_dealers`,
`PATCH` уточняет стол. Дилер читает свои назначения через
`GET /me/dealer-assignments`.

Живое рабочее место дилера **не потребовало ни одной серверной строки**: оно
собрано из уже существующего `LiveState` — `clock` с уровнями и `tables` с
местами. Стоит помнить при отладке: `GET /tournaments/{id}/live` отдаётся
**без гарда**, а сокет `/live` требует JWT на handshake, но роль при входе в
комнату не проверяет.

## Что рядом

- [Клуб — залы, блайнды, настройки](https://docs.novapoker.ru/product/club.md) — то же словами продукта.
- [Дилеры на турнире](https://docs.novapoker.ru/product/dealers.md) — что видит дилер.
- [Live-цикл и реалтайм](https://docs.novapoker.ru/dev/live.md) — состав `LiveState`.
- [poker-organizer](https://docs.novapoker.ru/dev/poker-organizer.md) — как запустить приложение.
