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

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

Клуб в коде — Organizer. Всё, что делает клуб, проходит проверку OrganizersService.requireRole, поэтому раздел стоит читать вместе с аутентификацией и ролями.

Поверх обычной авторизации приложение организатора держит оргконтекст: хук 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.

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