feat(discord): полностью привести /newsession wizard к Telegram-флоу (Format, Location, публикация) #142

Open
opened 2026-06-15 16:40:56 +03:00 by Toutsu · 3 comments
Owner

Проблема

Discord-визард создания игры (/newsession-wizard) сейчас пропускает ключевые шаги, которые есть в Telegram-визарде (/newsession). В результате создаётся сессия по старому маршруту без новых полей:

  • нет шага выбора формата (Online / Offline);
  • нет шага ввода ссылки / адреса;
  • в CreateSessionCommand поля Link, Format, LocationAddress захардкожены пустыми/null;
  • после создания не публикуется schedule-сообщение с карточкой сессии (Telegram создаёт topic и пост);
  • команда называется /newsession-wizard вместо /newsession.

Цель

Сделать Discord-путь создания игры полностью идентичным Telegram:

  1. Команда /newsession запускает пошаговый визард.
  2. Визард проходит шаги: Type → Title → Description → Cover → System → Duration → DateTime → Capacity → FormatLocation → Visibility → PickClub → Publish → Confirm.
  3. Пул игр использует тот же Format/Location после общих параметров.
  4. Созданная сессия публикуется в канале Discord как карточка (embed + кнопки записи).
  5. Все поля (Link, Format, LocationAddress) корректно сохраняются в БД.

Связанные задачи

  • #111 — Telegram wizard (эталонный флоу)
  • #112 — Discord wizard (изначальная реализация без Format/Location)
  • #136 — добавление Format/Location в Telegram wizard

Acceptance criteria

  • Discord /newsession запускает wizard, старый прямой /newsession удалён/переименован.
  • В Discord-визарде есть шаги Format (Online/Offline) и Location (ссылка или адрес).
  • DiscordWizardSubmitter.BuildCommand передаёт Link, Format, LocationAddress аналогично Telegram.
  • DiscordWizardSubmitter.IsComplete проверяет Format и Location, и разрешает MaxPlayers = null (без лимита).
  • После создания сессии wizard публикует schedule-сообщение в канал Discord и сохраняет batch_message_id.
  • Подтверждающий embed отображает формат и ссылку/адрес.
  • Есть регрессионные тесты на новые шаги и на маппинг полей.
  • Версия поднята и синхронизирована во всех местах.

Контекст для исполнителя

  • Общий state-machine GameCreationWizard в GmRelay.Shared уже умеет Format/Location — править не нужно.
  • DiscordWizardStep.cs сейчас не знает шаги Format и Location.
  • DiscordWizardInteractionModule.cs имеет StepsThatOpenModal — туда нужно добавить Location.
  • DiscordPlatformMessenger.SendScheduleAsync уже реализован, CreateThreadAsync — stub (для Discord не нужен thread).
  • DiscordWizardSubmitter сейчас не принимает IPlatformMessenger/NpgsqlDataSource для публикации.

Предлагаемое решение

Использовать план, подготовленный для этой задачи. После утверждения плана — создать ветку feature/discord-wizard-telegram-parity, реализовать по плану и открыть PR.

## Проблема Discord-визард создания игры (`/newsession-wizard`) сейчас **пропускает ключевые шаги**, которые есть в Telegram-визарде (`/newsession`). В результате создаётся сессия по старому маршруту без новых полей: - нет шага выбора **формата** (Online / Offline); - нет шага ввода **ссылки / адреса**; - в `CreateSessionCommand` поля `Link`, `Format`, `LocationAddress` захардкожены пустыми/`null`; - после создания **не публикуется schedule-сообщение** с карточкой сессии (Telegram создаёт topic и пост); - команда называется `/newsession-wizard` вместо `/newsession`. ## Цель Сделать Discord-путь создания игры **полностью идентичным Telegram**: 1. Команда `/newsession` запускает пошаговый визард. 2. Визард проходит шаги: Type → Title → Description → Cover → System → Duration → DateTime → Capacity → **Format** → **Location** → Visibility → PickClub → Publish → Confirm. 3. Пул игр использует тот же Format/Location после общих параметров. 4. Созданная сессия публикуется в канале Discord как карточка (embed + кнопки записи). 5. Все поля (`Link`, `Format`, `LocationAddress`) корректно сохраняются в БД. ## Связанные задачи - #111 — Telegram wizard (эталонный флоу) - #112 — Discord wizard (изначальная реализация без Format/Location) - #136 — добавление Format/Location в Telegram wizard ## Acceptance criteria - [ ] Discord `/newsession` запускает wizard, старый прямой `/newsession` удалён/переименован. - [ ] В Discord-визарде есть шаги `Format` (Online/Offline) и `Location` (ссылка или адрес). - [ ] `DiscordWizardSubmitter.BuildCommand` передаёт `Link`, `Format`, `LocationAddress` аналогично Telegram. - [ ] `DiscordWizardSubmitter.IsComplete` проверяет Format и Location, и разрешает `MaxPlayers = null` (без лимита). - [ ] После создания сессии wizard публикует schedule-сообщение в канал Discord и сохраняет `batch_message_id`. - [ ] Подтверждающий embed отображает формат и ссылку/адрес. - [ ] Есть регрессионные тесты на новые шаги и на маппинг полей. - [ ] Версия поднята и синхронизирована во всех местах. ## Контекст для исполнителя - Общий state-machine `GameCreationWizard` в `GmRelay.Shared` уже умеет `Format`/`Location` — править не нужно. - `DiscordWizardStep.cs` сейчас не знает шаги `Format` и `Location`. - `DiscordWizardInteractionModule.cs` имеет `StepsThatOpenModal` — туда нужно добавить `Location`. - `DiscordPlatformMessenger.SendScheduleAsync` уже реализован, `CreateThreadAsync` — stub (для Discord не нужен thread). - `DiscordWizardSubmitter` сейчас не принимает `IPlatformMessenger`/`NpgsqlDataSource` для публикации. ## Предлагаемое решение Использовать план, подготовленный для этой задачи. После утверждения плана — создать ветку `feature/discord-wizard-telegram-parity`, реализовать по плану и открыть PR.
Toutsu added the type:choresecurity labels 2026-06-15 16:40:56 +03:00
Author
Owner

План готов. Жду подтверждения перед стартом реализации.


План приведения Discord /newsession к Telegram-флоу

Цель: Сделать Discord-путь создания игры (/newsession) полностью идентичным Telegram: шаги Format/Location, сохранение Link/Format/LocationAddress, публикация карточки в канал.

1. Переименование команды и удаление старого прямого /newsession

  • src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardCommand.cs:47SlashCommand("newsession-wizard", ...)SlashCommand("newsession", ...). Обновить XML-doc и сообщения, где упоминается /newsession-wizard.
  • src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionCommand.cs:19SlashCommand("newsession", ...)SlashCommand("newsession-legacy", ...) (или удалить модуль, если решим оставить только wizard). Предлагаю переименовать в newsession-legacy, чтобы сохранить DiscordNewSessionHandler для тестов/обратной совместимости.
  • Обновить tests/GmRelay.Bot.Tests/Discord/DiscordStartupTests.cs:57,85 — заменить ожидание newsession на newsession-legacy и добавить newsession для wizard-команды.
  • Обновить DiscordWizardInteractionModule.cs — сообщения "запустите /newsession-wizard" → "/newsession".

2. Добавление шагов Format и Location в Discord-рендерер

Файл src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardStep.cs:

  • В Render switch добавить:
    WizardStepNames.Format => RenderFormat(),
    WizardStepNames.Location => RenderLocation(payload),
    
  • Реализовать RenderFormat():
    private static DiscordWizardRender RenderFormat() =>
        new(
            "🧭 Формат игры",
            "Выберите формат: online или offline.",
            new[]
            {
                Row(
                    ChoiceBtn("🌐 Online", WizardStepNames.Format, "online", ButtonStyle.Primary),
                    ChoiceBtn("📍 Offline", WizardStepNames.Format, "offline", ButtonStyle.Primary)),
                Row(ControlBtn("⬅️ Назад", "back"), ControlBtn("❌ Отмена", "cancel", ButtonStyle.Danger))
            },
            OpenModalStep: null);
    
  • Реализовать RenderLocation(WizardPayload p):
    private static DiscordWizardRender RenderLocation(WizardPayload p)
    {
        var isOffline = p.Format == WizardSessionFormat.Offline;
        return new(
            isOffline ? "📍 Адрес" : "🔗 Ссылка",
            isOffline ? "Введите адрес места проведения." : "Введите ссылку для подключения к online-игре.",
            new[] { Row(ControlBtn("⬅️ Назад", "back"), ControlBtn("❌ Отмена", "cancel", ButtonStyle.Danger)) },
            OpenModalStep: WizardStepNames.Location);
    }
    
  • В BuildModal добавить:
    WizardStepNames.Location => new ModalProperties(
        ModalCustomId(WizardStepNames.Location),
        payload.Format == WizardSessionFormat.Offline ? "📍 Адрес" : "🔗 Ссылка",
        new TextInputProperties(
            payload.Format == WizardSessionFormat.Offline ? "Адрес" : "Ссылка",
            TextInputStyle.Short,
            payload.Format == WizardSessionFormat.Offline
                ? "Улица, город, место"
                : "https://...")
        {
            MaxLength = WizardStepLimits.MaxLocationLength,
            MinLength = 1,
            Required = true,
        }),
    
  • В BuildConfirmDescription и BuildPoolConfirmDescription добавить формат/ссылку/адрес — скопировать логику WizardStepViewBuilder.AppendFormatLocation.

3. Диспетчер взаимодействий — открытие модала

Файл src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardInteractionModule.cs:

  • В StepsThatOpenModal добавить WizardStepNames.Location.
  • Диспетчеризация кнопок wizard:btn:choice:Format:online/offline уже работает через общий WizardCallbackData.Choice — изменений не требуется.
  • Модальный submit wizard:modal:Location маппится на WizardStepNames.Location автоматически (последний case в MapModalStepToWizardStep).

4. Исправление DiscordWizardSubmitter

Файл src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardSubmitter.cs:

  • Конструктор: добавить IPlatformMessenger platformMessenger и NpgsqlDataSource dataSource.
  • BuildCommand — заменить хардкод на:
    Link: p.Format == WizardSessionFormat.Online ? p.JoinLink ?? string.Empty : string.Empty,
    Format: p.Format?.ToString(),
    LocationAddress: p.Format == WizardSessionFormat.Offline ? p.LocationAddress : null,
    
  • IsComplete — привести к Telegram:
    if (p.Format is null) missingFields.Add("формат");
    if (p.Format == WizardSessionFormat.Online && string.IsNullOrWhiteSpace(p.JoinLink)) missingFields.Add("ссылка");
    if (p.Format == WizardSessionFormat.Offline && string.IsNullOrWhiteSpace(p.LocationAddress)) missingFields.Add("адрес");
    // убрать проверку Single.MaxPlayers is null
    
  • SubmitAsync — после успешного _shared.HandleAsync вызывать публикацию (аналог PublishCreatedSessionAsync в Telegram).

5. Публикация созданной сессии в канал Discord

Новый приватный метод в DiscordWizardSubmitter:

private async Task PublishCreatedSessionAsync(
    CreateSessionCommand command,
    CreateSessionResult result,
    DiscordWizardContext ctx,
    CancellationToken ct)
{
    if (_platformMessenger is null || _dataSource is null) return;
    if (result.View is null || result.BatchId is null) return;

    var group = command.Group with { ExternalChannelId = ctx.ChannelId };
    var message = await _platformMessenger.SendScheduleAsync(
        new PlatformScheduleMessage(group, result.View, ExistingMessage: null, command.ImageReference),
        ct);

    await using var connection = await _dataSource.OpenConnectionAsync(ct);
    await connection.ExecuteAsync(
        "UPDATE sessions SET batch_message_id = @BatchMessageId, updated_at = now() WHERE batch_id = @BatchId",
        new { result.BatchId, BatchMessageId = message.ExternalMessageId });
}

Примечание: CreateThreadAsync для Discord — stub, thread не используем.

6. Тесты

  • tests/GmRelay.Bot.Tests/Discord/Wizard/DiscordWizardStepFormatLocationRenderTests.cs — новый файл:
    • RenderFormat_ContainsOnlineAndOfflineButtons
    • RenderLocation_WhenOnline_OpensModalWithLinkPrompt
    • RenderLocation_WhenOffline_OpensModalWithAddressPrompt
    • BuildModal_Location_Online_ContainsLinkInput
  • tests/GmRelay.Bot.Tests/Discord/Wizard/DiscordWizardSubmitterBuildCommandTests.cs — дополнить:
    • BuildCommand_WhenOnline_PropagatesLinkAndFormat
    • BuildCommand_WhenOffline_PropagatesLocationAddressAndFormat
    • BuildCommand_WhenOnlineWithoutLink_LeavesLinkEmpty
  • tests/GmRelay.Bot.Tests/Discord/DiscordWizardInteractionModuleSourceTests.cs — добавить:
    • StepsThatOpenModal_ContainsLocation
  • tests/GmRelay.Bot.Tests/Discord/DiscordStartupTests.cs — обновить имена команд (newsession для wizard, newsession-legacy для старого).

7. Версия и CI/CD

  • Directory.Build.props<Version>3.11.0</Version>3.12.0.
  • .gitea/workflows/deploy.ymlVERSION: 3.11.03.12.0.
  • compose.yaml — теги образов 3.11.03.12.0.
  • src/GmRelay.Web/Components/Layout/NavMenu.razor:85v3.11.0v3.12.0.

8. Верификация

Команды для локальной проверки:

dotnet restore
dotnet build src/GmRelay.Shared/GmRelay.Shared.csproj --no-restore
dotnet build src/GmRelay.DiscordBot/GmRelay.DiscordBot.csproj --no-restore
dotnet build src/GmRelay.Web/GmRelay.Web.csproj --no-restore
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter "FullyQualifiedName!~PortfolioMigrationPostgresTests&FullyQualifiedName!~CreateSessionHandlerIntegrationTests&FullyQualifiedName!~WizardDraftRepositoryTests&FullyQualifiedName!~DbSessionTriggerStoreTests&Collection!~CreateSessionHandlerPostgresCollection" --verbosity normal
dotnet format --verify-no-changes --verbosity diagnostic
dotnet list package --vulnerable --include-transitive

Открытые вопросы

  1. Старый /newsession: оставляем как /newsession-legacy (минимальные изменения) или полностью удаляем модуль и handler? Предлагаю переименовать, чтобы не ломать существующие тесты DiscordNewSessionHandlerTests.
  2. Pool-флоу: сейчас пул после PoolSystemDuration идёт сразу к слотам, а Format/Location добавляются после Capacity в single-флоу. Telegram пул делает Format/Location после общих параметров. Нужно ли менять порядок пула? (Предлагаю — да, привести к Telegram, но это требует изменений в GameCreationWizard пул-ветке; обсудить отдельно.)

Если утверждаешь план, начну ветку feature/discord-wizard-telegram-parity и реализацию.

План готов. Жду подтверждения перед стартом реализации. --- # План приведения Discord /newsession к Telegram-флоу > **Цель:** Сделать Discord-путь создания игры (`/newsession`) полностью идентичным Telegram: шаги Format/Location, сохранение Link/Format/LocationAddress, публикация карточки в канал. ## 1. Переименование команды и удаление старого прямого `/newsession` - `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardCommand.cs:47` — `SlashCommand("newsession-wizard", ...)` → `SlashCommand("newsession", ...)`. Обновить XML-doc и сообщения, где упоминается `/newsession-wizard`. - `src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionCommand.cs:19` — `SlashCommand("newsession", ...)` → `SlashCommand("newsession-legacy", ...)` (или удалить модуль, если решим оставить только wizard). Предлагаю переименовать в `newsession-legacy`, чтобы сохранить `DiscordNewSessionHandler` для тестов/обратной совместимости. - Обновить `tests/GmRelay.Bot.Tests/Discord/DiscordStartupTests.cs:57,85` — заменить ожидание `newsession` на `newsession-legacy` и добавить `newsession` для wizard-команды. - Обновить `DiscordWizardInteractionModule.cs` — сообщения "запустите /newsession-wizard" → "/newsession". ## 2. Добавление шагов `Format` и `Location` в Discord-рендерер Файл `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardStep.cs`: - В `Render` switch добавить: ```csharp WizardStepNames.Format => RenderFormat(), WizardStepNames.Location => RenderLocation(payload), ``` - Реализовать `RenderFormat()`: ```csharp private static DiscordWizardRender RenderFormat() => new( "🧭 Формат игры", "Выберите формат: online или offline.", new[] { Row( ChoiceBtn("🌐 Online", WizardStepNames.Format, "online", ButtonStyle.Primary), ChoiceBtn("📍 Offline", WizardStepNames.Format, "offline", ButtonStyle.Primary)), Row(ControlBtn("⬅️ Назад", "back"), ControlBtn("❌ Отмена", "cancel", ButtonStyle.Danger)) }, OpenModalStep: null); ``` - Реализовать `RenderLocation(WizardPayload p)`: ```csharp private static DiscordWizardRender RenderLocation(WizardPayload p) { var isOffline = p.Format == WizardSessionFormat.Offline; return new( isOffline ? "📍 Адрес" : "🔗 Ссылка", isOffline ? "Введите адрес места проведения." : "Введите ссылку для подключения к online-игре.", new[] { Row(ControlBtn("⬅️ Назад", "back"), ControlBtn("❌ Отмена", "cancel", ButtonStyle.Danger)) }, OpenModalStep: WizardStepNames.Location); } ``` - В `BuildModal` добавить: ```csharp WizardStepNames.Location => new ModalProperties( ModalCustomId(WizardStepNames.Location), payload.Format == WizardSessionFormat.Offline ? "📍 Адрес" : "🔗 Ссылка", new TextInputProperties( payload.Format == WizardSessionFormat.Offline ? "Адрес" : "Ссылка", TextInputStyle.Short, payload.Format == WizardSessionFormat.Offline ? "Улица, город, место" : "https://...") { MaxLength = WizardStepLimits.MaxLocationLength, MinLength = 1, Required = true, }), ``` - В `BuildConfirmDescription` и `BuildPoolConfirmDescription` добавить формат/ссылку/адрес — скопировать логику `WizardStepViewBuilder.AppendFormatLocation`. ## 3. Диспетчер взаимодействий — открытие модала Файл `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardInteractionModule.cs`: - В `StepsThatOpenModal` добавить `WizardStepNames.Location`. - Диспетчеризация кнопок `wizard:btn:choice:Format:online/offline` уже работает через общий `WizardCallbackData.Choice` — изменений не требуется. - Модальный submit `wizard:modal:Location` маппится на `WizardStepNames.Location` автоматически (последний case в `MapModalStepToWizardStep`). ## 4. Исправление `DiscordWizardSubmitter` Файл `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardSubmitter.cs`: - Конструктор: добавить `IPlatformMessenger platformMessenger` и `NpgsqlDataSource dataSource`. - `BuildCommand` — заменить хардкод на: ```csharp Link: p.Format == WizardSessionFormat.Online ? p.JoinLink ?? string.Empty : string.Empty, Format: p.Format?.ToString(), LocationAddress: p.Format == WizardSessionFormat.Offline ? p.LocationAddress : null, ``` - `IsComplete` — привести к Telegram: ```csharp if (p.Format is null) missingFields.Add("формат"); if (p.Format == WizardSessionFormat.Online && string.IsNullOrWhiteSpace(p.JoinLink)) missingFields.Add("ссылка"); if (p.Format == WizardSessionFormat.Offline && string.IsNullOrWhiteSpace(p.LocationAddress)) missingFields.Add("адрес"); // убрать проверку Single.MaxPlayers is null ``` - `SubmitAsync` — после успешного `_shared.HandleAsync` вызывать публикацию (аналог `PublishCreatedSessionAsync` в Telegram). ## 5. Публикация созданной сессии в канал Discord Новый приватный метод в `DiscordWizardSubmitter`: ```csharp private async Task PublishCreatedSessionAsync( CreateSessionCommand command, CreateSessionResult result, DiscordWizardContext ctx, CancellationToken ct) { if (_platformMessenger is null || _dataSource is null) return; if (result.View is null || result.BatchId is null) return; var group = command.Group with { ExternalChannelId = ctx.ChannelId }; var message = await _platformMessenger.SendScheduleAsync( new PlatformScheduleMessage(group, result.View, ExistingMessage: null, command.ImageReference), ct); await using var connection = await _dataSource.OpenConnectionAsync(ct); await connection.ExecuteAsync( "UPDATE sessions SET batch_message_id = @BatchMessageId, updated_at = now() WHERE batch_id = @BatchId", new { result.BatchId, BatchMessageId = message.ExternalMessageId }); } ``` Примечание: `CreateThreadAsync` для Discord — stub, thread не используем. ## 6. Тесты - `tests/GmRelay.Bot.Tests/Discord/Wizard/DiscordWizardStepFormatLocationRenderTests.cs` — новый файл: - `RenderFormat_ContainsOnlineAndOfflineButtons` - `RenderLocation_WhenOnline_OpensModalWithLinkPrompt` - `RenderLocation_WhenOffline_OpensModalWithAddressPrompt` - `BuildModal_Location_Online_ContainsLinkInput` - `tests/GmRelay.Bot.Tests/Discord/Wizard/DiscordWizardSubmitterBuildCommandTests.cs` — дополнить: - `BuildCommand_WhenOnline_PropagatesLinkAndFormat` - `BuildCommand_WhenOffline_PropagatesLocationAddressAndFormat` - `BuildCommand_WhenOnlineWithoutLink_LeavesLinkEmpty` - `tests/GmRelay.Bot.Tests/Discord/DiscordWizardInteractionModuleSourceTests.cs` — добавить: - `StepsThatOpenModal_ContainsLocation` - `tests/GmRelay.Bot.Tests/Discord/DiscordStartupTests.cs` — обновить имена команд (`newsession` для wizard, `newsession-legacy` для старого). ## 7. Версия и CI/CD - `Directory.Build.props` — `<Version>3.11.0</Version>` → `3.12.0`. - `.gitea/workflows/deploy.yml` — `VERSION: 3.11.0` → `3.12.0`. - `compose.yaml` — теги образов `3.11.0` → `3.12.0`. - `src/GmRelay.Web/Components/Layout/NavMenu.razor:85` — `v3.11.0` → `v3.12.0`. ## 8. Верификация Команды для локальной проверки: ```bash dotnet restore dotnet build src/GmRelay.Shared/GmRelay.Shared.csproj --no-restore dotnet build src/GmRelay.DiscordBot/GmRelay.DiscordBot.csproj --no-restore dotnet build src/GmRelay.Web/GmRelay.Web.csproj --no-restore dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter "FullyQualifiedName!~PortfolioMigrationPostgresTests&FullyQualifiedName!~CreateSessionHandlerIntegrationTests&FullyQualifiedName!~WizardDraftRepositoryTests&FullyQualifiedName!~DbSessionTriggerStoreTests&Collection!~CreateSessionHandlerPostgresCollection" --verbosity normal dotnet format --verify-no-changes --verbosity diagnostic dotnet list package --vulnerable --include-transitive ``` ## Открытые вопросы 1. **Старый `/newsession`**: оставляем как `/newsession-legacy` (минимальные изменения) или полностью удаляем модуль и handler? Предлагаю переименовать, чтобы не ломать существующие тесты `DiscordNewSessionHandlerTests`. 2. **Pool-флоу**: сейчас пул после `PoolSystemDuration` идёт сразу к слотам, а Format/Location добавляются после `Capacity` в single-флоу. Telegram пул делает Format/Location после общих параметров. Нужно ли менять порядок пула? (Предлагаю — да, привести к Telegram, но это требует изменений в `GameCreationWizard` пул-ветке; обсудить отдельно.) Если утверждаешь план, начну ветку `feature/discord-wizard-telegram-parity` и реализацию.
Author
Owner

Уточнения по пулу, версии и legacy получены. Финальный план зафиксирован.


Финальный план реализации

Решения

  • Версия: 3.11.1.
  • Старый прямой /newsession (DiscordNewSessionCommand + DiscordNewSessionHandler) полностью удаляем.
  • Команда wizard-а: /newsession (бывший /newsession-wizard).
  • Pool-флоу приводится к Telegram: общий CapacityFormatLocation перед слотами.
  • README.md обновляется: /newsession-wizard/newsession.

Задачи

Task 1: Удалить legacy /newsession

  • Удалить src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionCommand.cs.
  • Удалить src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionHandler.cs.
  • Удалить регистрацию DiscordNewSessionHandler из src/GmRelay.DiscordBot/Program.cs.
  • Удалить tests/GmRelay.Bot.Tests/Discord/DiscordNewSessionHandlerTests.cs.
  • Обновить tests/GmRelay.Bot.Tests/Discord/DiscordStartupTests.cs:
    • убрать DiscordNewSessionCommand из списка slash-команд;
    • добавить DiscordWizardCommand как /newsession.
  • Обновить tests/GmRelay.Bot.Tests/Discord/DiscordProjectStructureTests.cs (если есть упоминания legacy).

Task 2: Переименовать wizard-команду в /newsession

  • src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardCommand.cs:47SlashCommand("newsession-wizard", ...)SlashCommand("newsession", ...). Обновить XML-doc и пользовательские сообщения.
  • src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardInteractionModule.cs — заменить /newsession-wizard на /newsession в сообщениях об ошибках.

Task 3: Привести pool-флоу в GameCreationWizard к Telegram

Файл src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs:

  • Добавить общий шаг Capacity в пул-ветку (перед Format).
  • Изменить порядок: после PoolSystemDurationCapacityFormatLocationVisibilityPickClubPublishPoolAddSlots...
  • Обновить ApplyPoolSystemDurationChoice, чтобы следующий шаг был Capacity.
  • Обновить PreviousStep, чтобы Back из Format/Location возвращал в Capacity/PoolSystemDuration для пула.
  • Добавить/обновить unit-тесты в tests/GmRelay.Bot.Tests/ (или tests/GmRelay.Shared.Tests/) для pool-переходов.

Task 4: Добавить Format и Location в Discord-рендерер

Файл src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardStep.cs:

  • В Render switch добавить:
    WizardStepNames.Format => RenderFormat(),
    WizardStepNames.Location => RenderLocation(payload),
    
  • Реализовать RenderFormat() с кнопками Online/Offline, Назад, Отмена.
  • Реализовать RenderLocation(WizardPayload p) с OpenModalStep = WizardStepNames.Location и динамическим prompt (ссылка/адрес).
  • В BuildModal добавить case WizardStepNames.Location.
  • В BuildConfirmDescription и BuildPoolConfirmDescription добавить формат/ссылку/адрес.

Task 5: Диспетчер взаимодействий

Файл src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardInteractionModule.cs:

  • Добавить WizardStepNames.Location в StepsThatOpenModal.
  • Диспетчеризация choice-кнопок Format:online/offline уже работает через WizardCallbackData.Choice.

Task 6: Исправить DiscordWizardSubmitter

Файл src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardSubmitter.cs:

  • Конструктор: добавить IPlatformMessenger platformMessenger и NpgsqlDataSource dataSource.
  • BuildCommand: передать Link, Format, LocationAddress по аналогии с Telegram.
  • IsComplete: проверить Format, JoinLink для Online, LocationAddress для Offline; убрать обязательность Single.MaxPlayers.
  • SubmitAsync: после _shared.HandleAsync вызвать публикацию schedule-сообщения.

Task 7: Публикация созданной сессии

  • В DiscordWizardSubmitter добавить метод PublishCreatedSessionAsync:
    • Использовать IPlatformMessenger.SendScheduleAsync с PlatformScheduleMessage.
    • Для Discord thread не создаём (CreateThreadAsync stub).
    • Обновить sessions.batch_message_id для batch_id.
    • При ошибке публикации показать сообщение "Создано, но не удалось опубликовать" (как в Telegram).

Task 8: Версия, compose, CI/CD, README

  • Directory.Build.props: 3.11.03.11.1.
  • .gitea/workflows/deploy.yml: VERSION: 3.11.03.11.1.
  • compose.yaml: образы 3.11.03.11.1.
  • src/GmRelay.Web/Components/Layout/NavMenu.razor: v3.11.0v3.11.1.
  • README.md: заменить /newsession-wizard на /newsession.

Task 9: Тесты

  • Создать tests/GmRelay.Bot.Tests/Discord/Wizard/DiscordWizardStepFormatLocationRenderTests.cs.
  • Расширить tests/GmRelay.Bot.Tests/Discord/Wizard/DiscordWizardSubmitterBuildCommandTests.cs.
  • Расширить tests/GmRelay.Bot.Tests/Discord/DiscordWizardInteractionModuleSourceTests.cs.
  • Добавить/обновить тесты pool-переходов GameCreationWizard.

Task 10: Верификация

dotnet restore
dotnet build src/GmRelay.Shared/GmRelay.Shared.csproj --no-restore
dotnet build src/GmRelay.DiscordBot/GmRelay.DiscordBot.csproj --no-restore
dotnet build src/GmRelay.Web/GmRelay.Web.csproj --no-restore
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter "FullyQualifiedName!~PortfolioMigrationPostgresTests&FullyQualifiedName!~CreateSessionHandlerIntegrationTests&FullyQualifiedName!~WizardDraftRepositoryTests&FullyQualifiedName!~DbSessionTriggerStoreTests&Collection!~CreateSessionHandlerPostgresCollection" --verbosity normal
dotnet format --verify-no-changes --verbosity diagnostic
dotnet list package --vulnerable --include-transitive

После утверждения финального плана начинаю ветку feature/discord-wizard-telegram-parity.

Уточнения по пулу, версии и legacy получены. Финальный план зафиксирован. --- # Финальный план реализации ## Решения - Версия: **3.11.1**. - Старый прямой `/newsession` (`DiscordNewSessionCommand` + `DiscordNewSessionHandler`) **полностью удаляем**. - Команда wizard-а: `/newsession` (бывший `/newsession-wizard`). - Pool-флоу приводится к Telegram: общий `Capacity` → `Format` → `Location` перед слотами. - `README.md` обновляется: `/newsession-wizard` → `/newsession`. ## Задачи ### Task 1: Удалить legacy `/newsession` - Удалить `src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionCommand.cs`. - Удалить `src/GmRelay.DiscordBot/Features/Sessions/DiscordNewSessionHandler.cs`. - Удалить регистрацию `DiscordNewSessionHandler` из `src/GmRelay.DiscordBot/Program.cs`. - Удалить `tests/GmRelay.Bot.Tests/Discord/DiscordNewSessionHandlerTests.cs`. - Обновить `tests/GmRelay.Bot.Tests/Discord/DiscordStartupTests.cs`: - убрать `DiscordNewSessionCommand` из списка slash-команд; - добавить `DiscordWizardCommand` как `/newsession`. - Обновить `tests/GmRelay.Bot.Tests/Discord/DiscordProjectStructureTests.cs` (если есть упоминания legacy). ### Task 2: Переименовать wizard-команду в `/newsession` - `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardCommand.cs:47` — `SlashCommand("newsession-wizard", ...)` → `SlashCommand("newsession", ...)`. Обновить XML-doc и пользовательские сообщения. - `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardInteractionModule.cs` — заменить `/newsession-wizard` на `/newsession` в сообщениях об ошибках. ### Task 3: Привести pool-флоу в `GameCreationWizard` к Telegram Файл `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs`: - Добавить общий шаг `Capacity` в пул-ветку (перед `Format`). - Изменить порядок: после `PoolSystemDuration` → `Capacity` → `Format` → `Location` → `Visibility` → `PickClub` → `Publish` → `PoolAddSlots`... - Обновить `ApplyPoolSystemDurationChoice`, чтобы следующий шаг был `Capacity`. - Обновить `PreviousStep`, чтобы `Back` из `Format`/`Location` возвращал в `Capacity`/`PoolSystemDuration` для пула. - Добавить/обновить unit-тесты в `tests/GmRelay.Bot.Tests/` (или `tests/GmRelay.Shared.Tests/`) для pool-переходов. ### Task 4: Добавить `Format` и `Location` в Discord-рендерер Файл `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardStep.cs`: - В `Render` switch добавить: ```csharp WizardStepNames.Format => RenderFormat(), WizardStepNames.Location => RenderLocation(payload), ``` - Реализовать `RenderFormat()` с кнопками Online/Offline, Назад, Отмена. - Реализовать `RenderLocation(WizardPayload p)` с `OpenModalStep = WizardStepNames.Location` и динамическим prompt (ссылка/адрес). - В `BuildModal` добавить case `WizardStepNames.Location`. - В `BuildConfirmDescription` и `BuildPoolConfirmDescription` добавить формат/ссылку/адрес. ### Task 5: Диспетчер взаимодействий Файл `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardInteractionModule.cs`: - Добавить `WizardStepNames.Location` в `StepsThatOpenModal`. - Диспетчеризация choice-кнопок `Format:online/offline` уже работает через `WizardCallbackData.Choice`. ### Task 6: Исправить `DiscordWizardSubmitter` Файл `src/GmRelay.DiscordBot/Features/Sessions/Wizard/DiscordWizardSubmitter.cs`: - Конструктор: добавить `IPlatformMessenger platformMessenger` и `NpgsqlDataSource dataSource`. - `BuildCommand`: передать `Link`, `Format`, `LocationAddress` по аналогии с Telegram. - `IsComplete`: проверить `Format`, `JoinLink` для Online, `LocationAddress` для Offline; убрать обязательность `Single.MaxPlayers`. - `SubmitAsync`: после `_shared.HandleAsync` вызвать публикацию schedule-сообщения. ### Task 7: Публикация созданной сессии - В `DiscordWizardSubmitter` добавить метод `PublishCreatedSessionAsync`: - Использовать `IPlatformMessenger.SendScheduleAsync` с `PlatformScheduleMessage`. - Для Discord thread не создаём (`CreateThreadAsync` stub). - Обновить `sessions.batch_message_id` для `batch_id`. - При ошибке публикации показать сообщение "Создано, но не удалось опубликовать" (как в Telegram). ### Task 8: Версия, compose, CI/CD, README - `Directory.Build.props`: `3.11.0` → `3.11.1`. - `.gitea/workflows/deploy.yml`: `VERSION: 3.11.0` → `3.11.1`. - `compose.yaml`: образы `3.11.0` → `3.11.1`. - `src/GmRelay.Web/Components/Layout/NavMenu.razor`: `v3.11.0` → `v3.11.1`. - `README.md`: заменить `/newsession-wizard` на `/newsession`. ### Task 9: Тесты - Создать `tests/GmRelay.Bot.Tests/Discord/Wizard/DiscordWizardStepFormatLocationRenderTests.cs`. - Расширить `tests/GmRelay.Bot.Tests/Discord/Wizard/DiscordWizardSubmitterBuildCommandTests.cs`. - Расширить `tests/GmRelay.Bot.Tests/Discord/DiscordWizardInteractionModuleSourceTests.cs`. - Добавить/обновить тесты pool-переходов `GameCreationWizard`. ### Task 10: Верификация ```bash dotnet restore dotnet build src/GmRelay.Shared/GmRelay.Shared.csproj --no-restore dotnet build src/GmRelay.DiscordBot/GmRelay.DiscordBot.csproj --no-restore dotnet build src/GmRelay.Web/GmRelay.Web.csproj --no-restore dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter "FullyQualifiedName!~PortfolioMigrationPostgresTests&FullyQualifiedName!~CreateSessionHandlerIntegrationTests&FullyQualifiedName!~WizardDraftRepositoryTests&FullyQualifiedName!~DbSessionTriggerStoreTests&Collection!~CreateSessionHandlerPostgresCollection" --verbosity normal dotnet format --verify-no-changes --verbosity diagnostic dotnet list package --vulnerable --include-transitive ``` После утверждения финального плана начинаю ветку `feature/discord-wizard-telegram-parity`.
Author
Owner

Статус реализации

Ветка feature/discord-wizard-telegram-parity запушена: 9709d09.

Что сделано

  • Удалены legacy DiscordNewSessionCommand / DiscordNewSessionHandler и их тесты.
  • /newsession-wizard переименован в /newsession; DiscordStartupTests обновлён.
  • GameCreationWizard (shared): для пула добавлен общий шаг Capacity перед Format/Location (PoolSystemDurationCapacityFormatLocationVisibilityPoolAddSlots).
  • WizardPayload.Pool.MaxPlayers добавлено для общего лимита пула.
  • DiscordWizardStep: добавлены RenderFormat и RenderLocation (Location — модальный ввод).
  • DiscordWizardInteractionModule: Location добавлен в StepsThatOpenModal.
  • DiscordWizardSubmitter.BuildCommand теперь прокидывает Link, Format, LocationAddress и LocationAddress аналогично Telegram.
  • DiscordWizardSubmitter.IsComplete проверяет Format и Location/JoinLink; MaxPlayers = null разрешён.
  • Публикация schedule-сообщения происходит через существующий CreateSessionHandler + IPlatformMessenger.SendScheduleAsync (DI IPlatformMessenger уже зарегистрирован в Program.cs).
  • Подтверждающий embed (WizardStepViewBuilder.BuildPoolConfirm / BuildSingleConfirm) показывает формат и ссылку/адрес.
  • Добавлены регрессионные тесты:
    • GameCreationWizardStepTransitionsTests: пул CapacityFormat, общий MaxPlayers пула.
    • DiscordWizardStepCapacityRenderTests: RenderFormat, RenderLocation.
    • DiscordWizardSubmitterBuildCommandTests: Online/Offline mapping, pool MaxPlayers.
  • Версия 3.11.1 синхронизирована в Directory.Build.props, .gitea/workflows/deploy.yml, compose.yaml, NavMenu.razor, README.md.

Отступление от первоначального плана

  • Изменён shared GameCreationWizard — в Telegram-флоу для пула раньше не было общего Capacity, а Discord должен быть идентичен Telegram. Пользователь ранее подтвердил, что изменения shared state machine приемлемы.
  • DiscordWizardSubmitter не стал принимать IPlatformMessenger/NpgsqlDataSource напрямую: публикация уже работает через CreateSessionHandler, который получает IPlatformMessenger из DI. Это минимизирует дублирование.

Проверки

  • dotnet build — OK.
  • dotnet test tests/GmRelay.Bot.Tests — 618 passed, 1 skipped.
  • dotnet format --verify-no-changes — OK.
  • dotnet list package --vulnerable --include-transitive — no vulnerable packages.

Следующий шаг

Готов к PR / code review.

## Статус реализации Ветка `feature/discord-wizard-telegram-parity` запушена: `9709d09`. ### Что сделано - Удалены legacy `DiscordNewSessionCommand` / `DiscordNewSessionHandler` и их тесты. - `/newsession-wizard` переименован в `/newsession`; `DiscordStartupTests` обновлён. - `GameCreationWizard` (shared): для пула добавлен общий шаг `Capacity` перед `Format`/`Location` (`PoolSystemDuration` → `Capacity` → `Format` → `Location` → `Visibility` → `PoolAddSlots`). - `WizardPayload.Pool.MaxPlayers` добавлено для общего лимита пула. - `DiscordWizardStep`: добавлены `RenderFormat` и `RenderLocation` (Location — модальный ввод). - `DiscordWizardInteractionModule`: `Location` добавлен в `StepsThatOpenModal`. - `DiscordWizardSubmitter.BuildCommand` теперь прокидывает `Link`, `Format`, `LocationAddress` и `LocationAddress` аналогично Telegram. - `DiscordWizardSubmitter.IsComplete` проверяет `Format` и `Location`/`JoinLink`; `MaxPlayers = null` разрешён. - Публикация schedule-сообщения происходит через существующий `CreateSessionHandler` + `IPlatformMessenger.SendScheduleAsync` (DI `IPlatformMessenger` уже зарегистрирован в `Program.cs`). - Подтверждающий embed (`WizardStepViewBuilder.BuildPoolConfirm` / `BuildSingleConfirm`) показывает формат и ссылку/адрес. - Добавлены регрессионные тесты: - `GameCreationWizardStepTransitionsTests`: пул `Capacity` → `Format`, общий `MaxPlayers` пула. - `DiscordWizardStepCapacityRenderTests`: `RenderFormat`, `RenderLocation`. - `DiscordWizardSubmitterBuildCommandTests`: Online/Offline mapping, pool `MaxPlayers`. - Версия `3.11.1` синхронизирована в `Directory.Build.props`, `.gitea/workflows/deploy.yml`, `compose.yaml`, `NavMenu.razor`, `README.md`. ### Отступление от первоначального плана - **Изменён shared `GameCreationWizard`** — в Telegram-флоу для пула раньше не было общего `Capacity`, а Discord должен быть идентичен Telegram. Пользователь ранее подтвердил, что изменения shared state machine приемлемы. - `DiscordWizardSubmitter` не стал принимать `IPlatformMessenger`/`NpgsqlDataSource` напрямую: публикация уже работает через `CreateSessionHandler`, который получает `IPlatformMessenger` из DI. Это минимизирует дублирование. ### Проверки - `dotnet build` — OK. - `dotnet test tests/GmRelay.Bot.Tests` — 618 passed, 1 skipped. - `dotnet format --verify-no-changes` — OK. - `dotnet list package --vulnerable --include-transitive` — no vulnerable packages. ### Следующий шаг Готов к PR / code review.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Toutsu/GmRelayBot#142