8f0f2ef7e7
Moves the game-creation wizard state machine, view builder, and platform-neutral contracts (callback data, step names, storage exception, club option, step limits) from GmRelay.Bot to GmRelay.Shared. Telegram continues to work through a new TelegramWizardMessenger implementing IWizardMessenger and a WizardInteractionMapper that converts Update → WizardInteraction. Wires the new platform column on wizard_drafts (V032 migration) and switches chat/owner/thread/message ids to TEXT so the same table can hold Discord snowflakes later. - GameCreationWizard: now in Shared, takes IWizardMessenger + IWizardDraftRepository, dispatches on WizardInteraction. - New IWizardMessenger contract with Edit/Send/Answer/GetOwnerClubs (returns string ids so Telegram longs and Discord snowflakes both fit). - New WizardStepViewBuilder in Shared returns (text, IReadOnlyList<WizardAction>); TelegramWizardMessenger renders actions into InlineKeyboardMarkup via a new Bot-side ToInlineKeyboard helper. - New WizardInteractionMapper in Bot (5-case test) converts Telegram Update to WizardInteraction. - WizardDraft gains a Platform column; ChatId/MessageThreadId/OwnerId/ DraftMessageId switched to string. V032 migrates existing rows and rebuilds the owner lookup index on (platform, owner_id). - All existing wizard / create-session tests updated to the new contract (HandleInteractionAsync + WizardInteraction). Wizard callback-data format preserved. - dotnet build clean, dotnet format --verify-no-changes clean, all 101 wizard tests pass.
158 lines
8.9 KiB
Markdown
158 lines
8.9 KiB
Markdown
# Issue #112 — Wizard platform-neutral refactor
|
|
|
|
## Summary
|
|
Moved the game-creation wizard's state machine and view builder from `GmRelay.Bot`
|
|
to `GmRelay.Shared`, replacing the Telegram-typed `ITelegramWizardMessenger` /
|
|
`Update` / `Message` surface with a platform-neutral `IWizardMessenger` /
|
|
`WizardInteraction` contract. Telegram continues to work unchanged through a
|
|
new `TelegramWizardMessenger` adapter and `WizardInteractionMapper`. The wizard
|
|
core is now ready for a future Discord adapter without touching the state
|
|
machine, and a `platform` column on `wizard_drafts` discriminates drafts from
|
|
different messengers.
|
|
|
|
## Branch
|
|
`feat/issue-112-wizard-refactor` (off `main`).
|
|
|
|
## Changed files
|
|
|
|
### New (Shared — platform-neutral wizard core)
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/IWizardMessenger.cs`
|
|
— `IWizardMessenger` (Edit/Send/Answer/GetOwnerClubs), `WizardAction` +
|
|
`WizardActionStyle`, `WizardKeyboard`, `WizardClubOption`, `WizardInteraction`,
|
|
`IWizardDraftRepository` (now takes `platform, ownerId`).
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardStepLimits.cs`
|
|
— `MaxTitleLength`, `MaxDescriptionLength`, `MaxSystemLength`, capacity and
|
|
duration bounds (moved out of the Bot-only `WizardStep`).
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardStepNames.cs`
|
|
— moved from Bot (unchanged content).
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardCallbackData.cs`
|
|
— moved from Bot (unchanged content; `wizard:cancel` / `wizard:back` /
|
|
`wizard:choice:step:value` format preserved for back-compat).
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardStorageException.cs`
|
|
— moved from Bot (unchanged content).
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardStepViewBuilder.cs`
|
|
— new platform-neutral view builder; produces
|
|
`(string Text, IReadOnlyList<WizardAction> Actions)` for each step.
|
|
Replaces the Telegram-only `WizardStep.Render(text, InlineKeyboardMarkup)`.
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs`
|
|
— moved from Bot; rewritten to take `IWizardMessenger` +
|
|
`IWizardDraftRepository` and a `WizardInteraction` (no more
|
|
`Update`/`Message`/`CallbackQuery`).
|
|
|
|
### Updated (Shared)
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardDraft.cs`
|
|
— `ChatId`, `MessageThreadId`, `OwnerId`, `DraftMessageId` switched to
|
|
`string?` to fit both Telegram and Discord ids; new `Platform` field
|
|
(defaults to `"Telegram"` for backward compatibility with pre-V032 rows).
|
|
- `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/WizardDraftRepository.cs`
|
|
— `GetActiveAsync(platform, ownerId)`; selects/inserts the new `platform`
|
|
column and the renamed `owner_id`.
|
|
- (Deleted) `src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/IWizardDraftRepository.cs`
|
|
— merged into `IWizardMessenger.cs` to keep wizard contracts colocated.
|
|
|
|
### New (Bot — Telegram adapter)
|
|
- `src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardInteractionMapper.cs`
|
|
— converts `Telegram.Bot.Types.Update` → `WizardInteraction`. The single
|
|
bridge between Telegram's update type and the platform-neutral wizard.
|
|
- `src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/TelegramWizardMessenger.cs`
|
|
— rewritten to implement `IWizardMessenger`; serialises
|
|
`(WizardDraft, text, IReadOnlyList<WizardAction>)` to Telegram
|
|
`EditMessageText` / `SendMessage` / `AnswerCallbackQuery`. Club lookup
|
|
SQL unchanged.
|
|
|
|
### Updated (Bot)
|
|
- `src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardStep.cs`
|
|
— kept as the Telegram-side keyboard renderer. Re-exports the
|
|
`WizardStepLimits` constants (so legacy call sites still work) and now
|
|
delegates to `WizardStepViewBuilder.Build(...)` for the (text, actions)
|
|
pair, then converts actions to `InlineKeyboardMarkup`.
|
|
- `src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs`
|
|
— depends on `IWizardMessenger`; `StartWizardAsync` / `SubmitDraftAsync`
|
|
use the new messenger contract; sets `Platform = "Telegram"` on the
|
|
draft; uses string ids for `ChatId` / `MessageThreadId` / `OwnerId`.
|
|
- `src/GmRelay.Bot/Infrastructure/Telegram/UpdateRouter.cs` — takes the
|
|
Shared `GameCreationWizard`; uses `WizardInteractionMapper.TryMap` to
|
|
feed the wizard with a platform-neutral `WizardInteraction`. Draft
|
|
lookup uses `(platform, ownerId)`.
|
|
- `src/GmRelay.Bot/Program.cs` — DI: `IWizardMessenger` →
|
|
`TelegramWizardMessenger`; `GameCreationWizard` resolved from Shared.
|
|
- (Deleted) `src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/GameCreationWizard.cs`
|
|
- (Deleted) `src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardStepNames.cs`
|
|
- (Deleted) `src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardCallbackData.cs`
|
|
- (Deleted) `src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/WizardStorageException.cs`
|
|
- (Deleted) `src/GmRelay.Bot/Features/Sessions/CreateSession/Wizard/ITelegramWizardMessenger.cs`
|
|
|
|
### New (Bot migrations)
|
|
- `src/GmRelay.Bot/Migrations/V032__wizard_drafts_platform.sql`
|
|
— adds `platform TEXT NOT NULL DEFAULT 'Telegram'`; converts `chat_id`,
|
|
`message_thread_id`, `draft_message_id` from numeric types to `TEXT`;
|
|
renames `owner_telegram_id` → `owner_id` and converts to `TEXT`; rebuilds
|
|
the owner lookup index to use `(platform, owner_id)`.
|
|
|
|
### Tests
|
|
- `tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardTestFakes.cs`
|
|
— `FakeWizardMessenger` now implements `IWizardMessenger`; `NewDraft`
|
|
sets `Platform = "Telegram"` and uses string ids; new helper
|
|
factories `CallbackInteraction`, `TextInteraction`, `PhotoInteraction`
|
|
build the platform-neutral `WizardInteraction`; legacy
|
|
`CallbackUpdate` / `TextUpdate` helpers preserved for router-level
|
|
tests that still consume `Update`.
|
|
- `tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardDraftRepositoryFixture.cs`
|
|
— schema updated to TEXT columns + `platform` column.
|
|
- `tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardDraftRepositoryTests.cs`
|
|
— uses the new `GetActiveAsync(platform, ownerId)` signature and string
|
|
ids.
|
|
- All wizard / create-session test files updated to call
|
|
`wizard.HandleInteractionAsync(...)` with a `WizardInteraction` instead
|
|
of `wizard.HandleUpdateAsync(Update, …)`.
|
|
- `tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardInteractionMapperTests.cs` (new)
|
|
— 5 cases covering callback / text / photo / captioned-photo / empty
|
|
updates.
|
|
- `tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardStepRenderTests.cs`
|
|
— `NewDraft` updated to use string `ChatId`.
|
|
|
|
## Verification
|
|
|
|
- `dotnet build` — full solution builds with 0 warnings, 0 errors.
|
|
- `dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj` —
|
|
568/571 pass (2 pre-existing `[Fact(Skip = …)]` happy-path tests
|
|
skipped, 1 pre-existing test failure in
|
|
`CampaignTemplatesNavigationTests.NavMenu_ShouldExposeCurrentProjectVersion`
|
|
which expects the old `v3.7.1` string in `NavMenu.razor` after the
|
|
3.8.0 release bump in commit `71080ae` — unrelated to this refactor).
|
|
All 101 wizard / create-session tests pass.
|
|
- `dotnet format --verify-no-changes` — clean.
|
|
- `git grep "Telegram.Bot" src/GmRelay.Shared/` — empty.
|
|
- `git grep "NetCord" src/GmRelay.Bot/` — empty.
|
|
- `GameCreationWizard.cs` exists exactly once, in
|
|
`src/GmRelay.Shared/Features/Sessions/CreateSession/Wizard/`.
|
|
- `IWizardMessenger` lives in `src/GmRelay.Shared/...`;
|
|
`ITelegramWizardMessenger` is gone.
|
|
- `dotnet list package --vulnerable` — clean.
|
|
|
|
## Notes for the verifier
|
|
|
|
- The callback-data wire format (`wizard:cancel`, `wizard:back`,
|
|
`wizard:create`, `wizard:choice:{step}:{value}`) is unchanged; the
|
|
router-level `wizard:resume` / `wizard:reset` controls are still
|
|
produced by `UpdateRouter`.
|
|
- `WizardDraft.DraftMessageId` switched from `long?` to `string?` so the
|
|
same column can hold Discord's 64-bit snowflakes. V032 converts
|
|
`BIGINT → TEXT`; existing rows in the V031 schema (if any are still
|
|
live) survive the cast.
|
|
- The `FakeWizardMessenger` keeps the long-typed recording shape
|
|
(`Edits` / `Sends` tuples) so the existing assertions in
|
|
`CreateSessionHandlerSubmitMissingFieldsTests` etc. keep working
|
|
without rewrites. The fake converts `draft.ChatId` /
|
|
`draft.MessageThreadId` to long on the way out, matching the old test
|
|
contract.
|
|
- The Telegram-renderer Bot-side `WizardStep` keeps the same public
|
|
surface (`WizardStep.Render(draft, payload, clubs)` returning
|
|
`(string, InlineKeyboardMarkup)`) so call sites in
|
|
`UpdateRouter.TryHandleDraftControlCallbackAsync` and the
|
|
`CreateSessionHandler` continue to work. The view layer behind it is
|
|
now `WizardStepViewBuilder`.
|
|
- A future `DiscordWizardMessenger` and `DiscordWizardInteractionMapper`
|
|
can be added without any change to the Shared wizard; this is
|
|
deliberately not part of this task (the plan calls it out as Task 2).
|