Add Discord guild ID configuration and enhance event announcement options
Some checks failed
CI / backend (push) Has been cancelled
CI / frontend (push) Has been cancelled
CI / compose (push) Has been cancelled

This commit introduces the `DISCORD_GUILD_ID` environment variable to the configuration files, allowing for better integration with Discord for role synchronization. The event announcement functionality has been updated to include an option for the `@everyone` mention, which can be toggled during event creation. The backend logic has been modified to handle this new option, and corresponding updates have been made to the frontend to allow users to control the mention behavior. Additionally, tests have been added to ensure the correct functionality of these features.
This commit is contained in:
2026-07-19 11:34:11 +03:00
parent ae19c03542
commit c3624376b0
29 changed files with 1524 additions and 37 deletions

View File

@@ -6,7 +6,8 @@
Admin UI содержит пошаговый workflow и roster editor с same-role swap, резервом и запуском. Live, spectator и bracket используют реальные API-команды и realtime refresh; участник турнира автоматически открывает собственный матч, остальные попадают в spectator mode, а актуальная сетка и соседние параллельные матчи доступны прямо над серией. Demo fixtures остались только локальным showcase.
Добавлена роль Moderator: она имеет все операционные права Admin, но управление списком модераторов доступно только Admin. До состояния Live workflow можно откатить на один этап назад.
Добавлен опциональный Discord-анонс нового микса: после успешного создания Event backend через Bot REST API публикует локализованный embed с `@everyone` и кнопкой регистрации. Ошибка Discord логируется, но не отменяет создание события; без bot token и channel ID интеграция отключена.
Добавлен Discord-анонс нового микса: после успешного создания Event backend через Bot REST API публикует локализованный embed и кнопку регистрации. При создании staff может оставить включённой галочку уведомления `@everyone` или отправить тихий анонс без пинга. Ошибка Discord логируется, но не отменяет создание события; без bot token и channel ID интеграция отключена.
Добавлен PostgreSQL-backed Discord role worker. После подтверждения составов он создаёт и выдаёт роли по актуальным Team.Name, Captain и slot Tank/Damage/Support, учитывает live-переименование и аварийную замену, а при завершении/отмене/удалении/откате удаляет временные роли. Повтор jobs идемпотентен; guest и отсутствующие в guild игроки сохраняются как предупреждения и не блокируют микс.
## Подтверждённые требования
@@ -32,7 +33,8 @@ Admin UI содержит пошаговый workflow и roster editor с same-r
- PostgreSQL в том же Compose-стеке с persistent volume;
- мобильный и десктопный интерфейс.
- карточки отменённых событий отображаются красными, завершённых — зелёными, активных — розовыми с меткой LIVE.
- новый микс может автоматически анонсироваться в настроенном Discord-канале со ссылкой на регистрацию.
- новый микс автоматически анонсируется в настроенном Discord-канале со ссылкой на регистрацию; `@everyone` можно отключить при создании.
- подтверждённые составы автоматически проецируются в управляемые Discord-роли и очищаются по завершении их жизненного цикла.
## Исходный регламент
@@ -79,6 +81,6 @@ Admin UI содержит пошаговый workflow и roster editor с same-r
## Ближайший следующий шаг
Проверить миграцию `006_event_workflow.sql` и полный сценарий на staging с реальной PostgreSQL (`TEST_DATABASE_URL`), проверить тестовый Discord-анонс с production bot token/channel ID, затем провести первый пробный микс через production UI.
Проверить миграции `006_event_workflow.sql` и `011_discord_role_sync.sql` с реальной PostgreSQL (`TEST_DATABASE_URL`), затем на staging проверить анонс, подтверждение roster, rename/emergency substitution и cleanup Discord-ролей перед первым production-миксом.
Публичная вкладка FAQ описывает регистрацию, балансировку, полномочия капитанов, выбор карт, баны героев и подсчёт Bo3.

View File

@@ -114,10 +114,14 @@ Application-слой управляет единым versioned workflow собы
### Discord-уведомления
После успешного создания события application-слой вызывает consumer-owned `EventAnnouncer` port. Discord REST adapter публикует в настроенный текстовый канал локализованный RU/EN embed с `@everyone`, временем начала и ссылкой на `/events/{eventId}`. Внешний вызов имеет короткий timeout и выполняется только после сохранения события: ошибка Discord логируется, но не откатывает созданный микс и не превращает успешную команду в HTTP 500.
После успешного создания события application-слой вызывает consumer-owned `EventAnnouncer` port. Discord REST adapter публикует в настроенный текстовый канал локализованный RU/EN embed, время начала и ссылку на `/events/{eventId}`. Create-команда передаёт отдельный флаг: по умолчанию сообщение содержит разрешённый `@everyone`, а при отключённой галочке тот же анонс отправляется без mention. Флаг не сохраняется в Event, потому что обновления не создают повторный анонс. Внешний вызов имеет короткий timeout и выполняется только после сохранения события: ошибка Discord логируется, но не откатывает созданный микс и не превращает успешную команду в HTTP 500.
Интеграция опциональна и отключена, если bot token и channel ID не заданы. Bot token доступен только API-контейнеру; OAuth client secret по-прежнему используется исключительно для входа. Для анонсов не нужны Gateway intents или постоянно открытое Gateway-соединение.
После подтверждения roster PostgreSQL-backed worker синхронизирует Discord-роли по desired state. Он создаёт глобальные `Tank` / `Damage` / `Support`, временную роль с текущим `Team.Name` и отдельную `${Team.Name} Captain`; player ID связывается с Discord user ID через Account. Переименование команды и аварийная замена ставят новый reconcile job. Роли определяются по сохранённым Discord ID, поэтому одинаковые названия и повтор jobs не создают логических дубликатов.
Очередь использует `FOR UPDATE SKIP LOCKED`, timeout и retry/backoff для 429/5xx. Частичная недоступность Discord, guest или отсутствующий в guild пользователь не откатывают доменную команду; предупреждение сохраняется в job. При отмене, завершении, удалении или откате подтверждения event-specific team/captain roles удаляются. Общие ролевые назначения пересчитываются по всем другим активным миксам, поэтому параллельные события не снимают нужную роль. Role worker включается только при наличии `DISCORD_GUILD_ID` и не требует Gateway intents.
### Капитаны
Капитан — назначение внутри конкретной команды, а не глобальная роль аккаунта. Admin может назначить или заменить капитана только участником этой команды. Только текущий капитан выполняет командные действия драфта; Admin имеет аварийное право выполнить действие с обязательной записью в аудит.

View File

@@ -22,7 +22,13 @@
### Discord-анонс события
Созданный микс проецируется во внешний Discord-канал как информационный анонс со ссылкой на страницу Event. Анонс не является источником состояния регистрации: RSVP, доступность события и все переходы workflow по-прежнему определяет backend Mixmaker. Недоступность Discord не отменяет валидное создание Event.
Созданный микс проецируется во внешний Discord-канал как информационный анонс со ссылкой на страницу Event. Параметр команды создания определяет, сопровождается ли анонс уведомлением `@everyone`; он не является сохраняемым состоянием Event. Анонс не является источником состояния регистрации: RSVP, доступность события и все переходы workflow по-прежнему определяет backend Mixmaker. Недоступность Discord не отменяет валидное создание Event.
### Discord-роли состава
Подтверждённый Roster является источником desired state для внешних Discord-ролей. Каждый участник получает временную роль с `Team.Name`, роль своего slot-а Tank/Damage/Support, а капитан — отдельную `${Team.Name} Captain`. Discord role ID и выданные ботом назначения сохраняются отдельно от доменных Team и Account, чтобы повторная синхронизация была идемпотентной.
Team/captain roles принадлежат конкретному Event и удаляются при отмене, завершении, удалении или возврате к редактированию roster. Tank/Damage/Support общие для guild: после cleanup их участники пересчитываются по всем оставшимся активным событиям. Guest и пользователь, отсутствующий в guild, не блокируют подтверждение состава и фиксируются как предупреждение интеграции.
### Жизненный цикл события
@@ -38,7 +44,7 @@
8. `Completed` — выполнены все запланированные матчи;
9. `Cancelled` — Admin отменил событие; оно остаётся в календаре, но игровые действия заблокированы.
Переходы выполняются отдельными командами API с проверкой ожидаемой версии. До запуска скрима staff-пользователь может вернуться на один этап назад: черновик удаляется при возврате из `RostersDraft`, а состав снова разблокируется при возврате из `RostersConfirmed`. Возврат из `Live` и `Completed` запрещён. Нельзя начать скрим без минимум двух полных команд, валидного состава 1/2/2 и капитана в каждой команде. После `RostersConfirmed` обычная перестановка запрещена. Аварийная замена во время `Live` доступна staff-пользователю, должна сохранять ролевой состав и записывается в аудит. Полное удаление события является отдельной staff-командой и каскадно удаляет его RSVP, составы, roster draft, серии, турнир и event-scoped аудит.
Переходы выполняются отдельными командами API с проверкой ожидаемой версии. До запуска скрима staff-пользователь может вернуться на один этап назад: черновик удаляется при возврате из `RostersDraft`, а состав снова разблокируется при возврате из `RostersConfirmed`. Возврат из `Live` и `Completed` запрещён. Нельзя начать скрим без минимум двух полных команд, валидного состава 1/2/2 и капитана в каждой команде. После `RostersConfirmed` обычная перестановка запрещена. Аварийная замена во время `Live` доступна staff-пользователю, должна сохранять ролевой состав и записывается в аудит; если заменён капитан, назначение переходит вошедшему вместо него игроку. Полное удаление события является отдельной staff-командой и каскадно удаляет его RSVP, составы, roster draft, серии, турнир и event-scoped аудит.
### Team

View File

@@ -81,7 +81,8 @@
- повторная жеребьёвка с защитой от одинаковых составов;
- [x] Discord-анонс нового микса с `@everyone` и ссылкой на регистрацию;
- обновление анонса, напоминания, публикация составов и результатов;
- синхронизация временных Discord-ролей и командных каналов;
- [x] идемпотентная синхронизация временных Discord team/captain и Tank/Damage/Support ролей;
- создание и очистка временных командных голосовых каналов;
- импорт данных и резервное копирование.
## Идеи после MVP