Commit Graph

47 Commits

Author SHA1 Message Date
Toutsu 85ff3a7faf fix(discord): address code-review findings on wizard adapter (issue #112)
PR Checks / test-and-build (pull_request) Successful in 9m54s
VERDICT from verifier (D:\Projects\Game\docs\review-report.md):
REQUEST_CHANGES — wizard was functionally broken at runtime.

## Critical

C-1. Choice-button customId was missing the 'choice:' segment.
    ButtonCustomId emitted 'wizard:btn:<step>:<value>' but the
    dispatcher's switch matches parts[1] == 'choice'. Every choice
    button (D&D 5e, Pathfinder, Waitlist, Publish, Confirm) fell
    into the default branch and showed 'Unknown button'.

    Fix: split into 3 customId helpers:
      ChoiceButtonCustomId(step, value)       -> 'wizard:btn:choice:<step>:<value>'
      ControlButtonCustomId(action)            -> 'wizard:btn:<action>:1'  (back/cancel/skip/create)
      ModalTriggerButtonCustomId(modalStep)    -> 'wizard:btn:modal:<modalStep>'
    Bulk-rewrote all 66 Btn() call sites in DiscordWizardStep.cs.

C-2. "Другое…" modal-trigger buttons were unrouted in dispatcher.
    Added 'parts[1] == "modal"' branch that opens the modal via
    InteractionCallback.Modal(BuildModal(parts[2], draft.ChatId)).

C-3. DiscordWizardSubmitter was leaking ex.Message from
    CreateSessionHandler to the user-visible draft embed. Postgres
    exceptions expose schema/constraint names. Replaced with
    generic user-facing error; full exception still logged
    server-side on the existing catch block.

## I-3 — parser-roundtrip tests (the gap that let C-1/C-2 through)

Added two real behavioural tests (not string-grep) to
DiscordWizardInteractionModuleSourceTests:
  - Renderer_And_Dispatcher_Agree_On_Wire_Format
  - ControlButtons_Are_Parsed_As_Control_Not_Choice
These mirror NetCord's [ComponentInteraction("wizard")] prefix
strip, run the parser, and assert the dispatcher would route to
the right branch. Catches the entire class of 'renderer and
dispatcher disagree on the wire format' regressions.

## I-6 — BuildResumeRow (cascading fix from C-1)

After C-1, BuildResumeRow's ButtonCustomId('cancel', '1') would
emit the wrong format. Switched to direct format strings
('wizard:btn:cancel:1', 'wizard:btn:resume:continue', etc.) which
match the dispatcher's 'back'/'cancel'/'create'/'resume' cases
directly, not the 'choice' prefix.

## Version sync (3.8.0 -> 3.9.0)

Directory.Build.props: <Version>3.9.0</Version>
compose.yaml: all 3 image tags -> 3.9.0
Version_ShouldBeSynchronizedForDiscordFeatureRelease test now green.

## Stats

build: 0 warnings, 0 errors
format: 0 of 279 files need changes
tests: 583 passed, 2 skipped (pre-existing), 0 failed
files: 7 changed, 226 +, 79 -
2026-06-05 23:09:24 +03:00
Coder f0952096f3 feat(discord): wizard interaction handlers + DI for StringMenu/Modal (issue #112)
Adds the missing inbound handlers for the Discord wizard that the
previous commit (b81d865) left out. Three thin NetCord module classes
share one WizardInteractionDispatcher:

- DiscordWizardButtonModule
- DiscordWizardStringMenuModule
- DiscordWizardModalModule

Each registers a single [ComponentInteraction("wizard")] method that
hands the args string to the dispatcher. The dispatcher parses the
custom-id tail (btn:choice:<step>:<value>, btn:back, btn:cancel,
btn:create, btn:resume:continue, btn:resume:restart, select:<step>,
modal:<step>), looks up the active draft by (platform="Discord",
ownerId=userId), and routes through the shared
GameCreationWizard.HandleInteractionAsync. The "create" callback
delegates to DiscordWizardSubmitter.SubmitAsync (3-retry finalize).

Program.cs gets 4 new singleton registrations (the dispatcher plus
the three module classes) and 2 new AddComponentInteractions calls
(StringMenu + Modal). The existing Button registration is unchanged.

12 new source-level smoke tests in DiscordWizardInteractionModuleSourceTests
cover the file shape: 3 handler classes, 3 base classes, 3
[ComponentInteraction] registrations, all 5 callback kinds parsed,
GameCreationWizard wired in, submitter invoked on create, Program.cs
registers all 3 AddComponentInteractions and all 4 module classes,
draft lookup by GetActiveAsync("Discord", ...), modal walks
Components[0] -> TextInput -> .Value, string menu reads
SelectedValues[0].

Build green. 190/190 Discord+Wizard tests pass (2 pre-existing
skipped). dotnet format clean.
2026-06-05 18:31:47 +03:00
Toutsu 2b725708ef test(discord): keep Moscow time parsing fixture in future 2026-06-01 20:00:59 +03:00
Toutsu 0c1d3abd7e feat(web): add public master profiles
PR Checks / test-and-build (pull_request) Successful in 12m32s
Add sanitized public GM profiles with publication controls, public /gm/{slug} pages, and links from public game surfaces.

Bump version -> 3.5.0
2026-05-29 00:08:14 +03:00
Toutsu a63e3bef1e fix: финальные правки ревью для issue #39
PR Checks / test-and-build (pull_request) Successful in 13m16s
- PublicSession.razor: добавлена обработка ?register=1, AuthStateProvider,
  TryGetPlatformIdentity, кнопки записи для авторизованных/неавторизованных
  пользователей, отображение результата регистрации
- CreateSessionHandler: добавлен cover_image_url в INSERT SQL
- DiscordProjectStructureTests: версия 3.4.0 во всех проверках
- README.md: актуальная версия v3.4.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 17:00:33 +03:00
Toutsu 3418d1a46c feat: add public club pages
PR Checks / test-and-build (pull_request) Successful in 12m47s
Add publication settings for clubs and sessions, read-only public club/session pages, dashboard controls, privacy-focused public queries, docs, and tests.

Bump version to 3.3.0
2026-05-28 12:23:47 +03:00
Toutsu fac5d75c7e Fix Discord co-GM management
Deploy Telegram Bot / build-and-push (push) Successful in 5m58s
Deploy Telegram Bot / scan-images (push) Successful in 3m39s
Deploy Telegram Bot / deploy (push) Successful in 34s
2026-05-27 16:32:47 +03:00
Toutsu 542f15f2d6 refactor: extract remaining Telegram handlers to platform-neutral contracts
PR Checks / test-and-build (pull_request) Successful in 13m48s
- Extract CreateSessionHandler, ListSessionsHandler, DeleteSessionHandler,
  ExportCalendarHandler, HandleRescheduleTimeInputHandler,
  HandleRescheduleVoteHandler to GmRelay.Shared
- Add IPlatformMessenger methods: SendScheduleAsync, UpdateScheduleAsync,
  SendGroupMessageAsync with actions, CreateThreadAsync, DeleteThreadAsync
- Rewrite Telegram Bot wrappers as thin adapters delegating to shared handlers
- Rewrite DiscordRescheduleVoteHandler to use shared HandleRescheduleVoteHandler
- Update UpdateRouter with explicit type aliases for ambiguous handler names
- Add contract and source-inspection tests for extracted handlers
- Bump version 3.1.1 → 3.2.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:52:09 +03:00
Toutsu 383e2c1d8d fix: create Telegram topics for template batches
PR Checks / test-and-build (pull_request) Successful in 12m56s
Create a Telegram forum topic when Web creates a batch from a campaign template, persist thread ownership on the generated sessions, and send the batch schedule into that topic.

Bump version -> 3.1.1
2026-05-27 13:50:18 +03:00
Toutsu c69ebf6c03 test: update DiscordProjectStructureTests version asserts to 3.1.0
PR Checks / test-and-build (pull_request) Successful in 13m24s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 16:58:18 +03:00
Toutsu 9fc434b42b fix(discord): treat /newsession and /reschedule input as Moscow time (UTC+3)
Deploy Telegram Bot / build-and-push (push) Successful in 6m15s
Deploy Telegram Bot / scan-images (push) Successful in 3m20s
Deploy Telegram Bot / deploy (push) Successful in 34s
DiscordNewSessionHandler.ParseTimeInput used DateTimeStyles.AssumeUniversal,
which interpreted user input as UTC. A user entering 15:00 got a session
scheduled at 18:00 MSK after rendering. Align with Telegram behavior by
treating input as Moscow time and converting to UTC before storage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 15:12:00 +03:00
Toutsu c2cc7fd9a8 fix(web): show discord sessions and integration labels
Deploy Telegram Bot / build-and-push (push) Successful in 5m46s
Deploy Telegram Bot / scan-images (push) Successful in 3m29s
Deploy Telegram Bot / deploy (push) Successful in 29s
2026-05-26 14:43:33 +03:00
Toutsu 3447acd8c4 fix(discord): update sessions via interactions
Deploy Telegram Bot / build-and-push (push) Successful in 6m14s
Deploy Telegram Bot / scan-images (push) Successful in 3m12s
Deploy Telegram Bot / deploy (push) Successful in 31s
2026-05-26 14:24:06 +03:00
Toutsu 6ed0a120a0 fix(discord): avoid duplicate schedule send after new session
Deploy Telegram Bot / build-and-push (push) Successful in 6m0s
Deploy Telegram Bot / scan-images (push) Successful in 3m22s
Deploy Telegram Bot / deploy (push) Successful in 29s
2026-05-26 13:40:59 +03:00
Toutsu c955e1572f fix(db): make legacy telegram_* columns nullable for Discord multi-platform
PR Checks / test-and-build (pull_request) Successful in 18m38s
V023 migration drops NOT NULL constraints on:
- game_groups.telegram_chat_id
- game_groups.gm_telegram_id
- players.telegram_id

This allows Discord (and future platforms) to create players and
game_groups without legacy Telegram identifiers.

Bump version → 3.0.10

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:51:45 +03:00
Toutsu dcbd9bab41 fix(discord): add missing Dapper.AOT reference to DiscordBot project
PR Checks / test-and-build (pull_request) Successful in 11m4s
GmRelay.Shared references Dapper.AOT with PrivateAssets=all, which
prevents the runtime DLL from flowing to downstream projects. Telegram
bot works because it explicitly references Dapper.AOT directly, but
Discord bot did not — causing FileNotFoundException for Dapper.AOT
at runtime, breaking the scheduler and slash commands.

- Add Dapper.AOT 1.0.48 to GmRelay.DiscordBot.csproj
- Add regression test: DiscordWorkerProject_ShouldExist asserts
  Dapper.AOT is present in the DiscordBot csproj
- Bump version → 3.0.9

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:20:56 +03:00
Toutsu 47d106e288 fix(tests): update DiscordNewSessionHandlerTests for deferred response pattern
PR Checks / test-and-build (pull_request) Successful in 11m55s
The Command_ShouldRenderEmbedOnSuccess test asserted the presence of
WithEmbeds in DiscordNewSessionCommand.cs. After switching to deferred
responses (InteractionCallback.DeferredMessage + ModifyResponseAsync),
embeds are now set via message.Embeds = embeds instead.

Bump version → 3.0.8

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:33:03 +03:00
Toutsu a5624897e9 fix(discord): add console logging and deferred responses
PR Checks / test-and-build (pull_request) Failing after 12m3s
- Add builder.Logging.AddConsole() to DiscordBot Program.cs so logs
  are visible in docker logs.
- Add granular LogInformation/LogError calls to DiscordNewSessionCommand
  and DiscordRescheduleCommand to diagnose failures.
- Use InteractionCallback.DeferredMessage() + ModifyResponseAsync pattern
  for /newsession and /reschedule to avoid Discord 3-second interaction
  timeout.
- Bump version → 3.0.8

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:18:09 +03:00
Toutsu 2942da0c35 fix(discord): use GuildInteractionUser.Permissions instead of REST guild lookup
PR Checks / test-and-build (pull_request) Successful in 11m25s
Replace REST GetGuildAsync/GetGuildUserAsync calls with authoritative
member.Permissions from the slash-command interaction payload. Discord
already resolves channel/guild permissions in the interaction JSON, so
we no longer need to fetch the guild via REST (which returns 404 when
the bot is not a REST member of the guild, e.g. user-installed apps).

Keep a best-effort GetGuildAsync call only to obtain OwnerId for the
permission checker fallback, swallowing 404 silently.

Bump version → 3.0.7

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 10:44:59 +03:00
Toutsu dd9337dd20 fix(discord): cast COUNT to int for slash command list query
PR Checks / test-and-build (pull_request) Successful in 9m34s
PostgreSQL COUNT() returns bigint, but DiscordSessionListItemDto expects
int for PlayerCount and WaitlistCount. Dapper 2.1.72 in GmRelay.DiscordBot
(without Dapper.AOT) fails to materialize the record with bigint→int mismatch.
Added ::int casts to both COUNT expressions.

Bump version to 3.0.6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 10:10:13 +03:00
Toutsu f6d5281af8 fix(discord): resolve slash commands from interaction payload instead of gateway cache
PR Checks / test-and-build (pull_request) Successful in 8m46s
Context.Guild in NetCord resolves the Guild object from the gateway client cache
(cache.Guilds.GetValueOrDefault(guildId)), not from the interaction JSON payload.
After a bot restart, the guild may not yet be cached when the first slash command
arrives, causing Context.Guild to be null even though the command is invoked
inside a guild channel. This produced "This command can only be used in a guild."

Changes:
- DiscordListSessionsCommand: use Context.Interaction.GuildId instead of Context.Guild.Id
- DiscordNewSessionCommand: use Context.Interaction.GuildId + REST GetGuildAsync/GetGuildUserAsync
- DiscordRescheduleCommand: same as above
- DiscordSessionInteractionModule: same fix for button interactions (CreateInput)
- Add null guard in GetResolvedPermissions for safety
- Bump version to 3.0.5

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 18:01:53 +03:00
Toutsu 9bd5fe75c9 test: sync version assertions to 3.0.4
PR Checks / test-and-build (pull_request) Successful in 8m35s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 17:08:01 +03:00
Toutsu 0b45aee96d fix(discord): declare slash commands on module methods
PR Checks / test-and-build (pull_request) Successful in 8m26s
2026-05-25 16:27:29 +03:00
Toutsu eff0128d29 fix(discord): register slash command modules
PR Checks / test-and-build (pull_request) Successful in 8m27s
Register NetCord application command modules after the host is built so module-based commands are published alongside the minimal /ping command.

Update README Discord env guidance to avoid the unused DISCORD_BOT_CLIENT_ID variable.

Bump version to 3.0.2.
2026-05-25 15:49:36 +03:00
Toutsu 8214e052af bump: version 3.0.1
Deploy Telegram Bot / build-and-push (push) Successful in 4m55s
Deploy Telegram Bot / scan-images (push) Successful in 2m2s
Deploy Telegram Bot / deploy (push) Successful in 28s
Synchronize version across all files:
- Directory.Build.props → 3.0.1
- compose.yaml → gmrelay-bot/web/discord-bot:3.0.1
- deploy.yml → VERSION: 3.0.1
- NavMenu.razor → v3.0.1
- DiscordProjectStructureTests → 3.0.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 15:34:25 +03:00
Toutsu baa25f2e1e feat: unify Telegram and Discord accounts via identity linking
PR Checks / test-and-build (pull_request) Successful in 7m6s
- Add V020 migration: player_links + identity_audit_log tables
- Add ISessionStore methods: ResolveEffectivePlayerId, LinkIdentity, UnlinkIdentity, GetLinkedIdentities
- Update SessionService to resolve effective player id for all permission checks
- Add /auth/discord/callback linking flow when already authenticated
- Add /api/me/identities GET/DELETE endpoints
- Add Profile.razor page for managing linked accounts
- Update NavMenu with profile link and v3.0.0 badge
- Bump version to 3.0.0 across all files

Bump version → 3.0.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 13:51:10 +03:00
Toutsu 72a392e652 fix: Discord OAuth CSRF cookie SameSite=None for cross-site callback
PR Checks / test-and-build (pull_request) Successful in 6m34s
- Changed __DiscordOAuthState cookie from SameSite=Strict to SameSite=None
  because Discord redirects from discord.com (cross-site) and Strict
  prevents the cookie from being sent on the callback request.
- Added logging for CSRF validation failure to aid future diagnostics.

Bump version → 2.8.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 13:08:14 +03:00
Toutsu 50f5307aac feat(web): finalize Discord OAuth and platform-agnostic auth
PR Checks / test-and-build (pull_request) Successful in 5m47s
- Bump version to 2.8.0 across all versioned files
- Fix AuthorizedSessionServiceTests for platform-agnostic identity
- Update Razor Pages to use *ForCurrentUserAsync APIs
- Add backward-compatible constructors to WebGameGroup/WebGroupManager
- Make DiscordOAuthOptions properties non-required for config binding

Bump version → 2.8.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 11:47:54 +03:00
Toutsu 1853a7a9c7 chore(release): bump version to 2.7.2
PR Checks / test-and-build (pull_request) Successful in 8m3s
Issue: #33
2026-05-21 15:36:17 +03:00
Toutsu fe8d5fe026 test(discord): update version assertions to 2.7.1
PR Checks / test-and-build (pull_request) Successful in 7m7s
Issue #32
2026-05-21 14:26:02 +03:00
Toutsu a2fa9aaa6c chore(release): bump version to 2.7.1
Issue #32
2026-05-21 14:24:17 +03:00
Toutsu 5b65ac4a2f chore(discord): add healthcheck to compose.yaml discord service
Issue #32
2026-05-21 14:21:35 +03:00
Toutsu feb3e08b63 feat(discord): register health check hosted service in Program.cs
Issue #32
2026-05-21 14:20:40 +03:00
Toutsu 08ffc6694e chore(discord): add DISCORD_BOT_TOKEN to .env.example
Issue #32
2026-05-21 14:19:02 +03:00
Toutsu 2a707e4825 feat(platform): route scheduler notifications through platform messenger
PR Checks / test-and-build (pull_request) Successful in 7m9s
2026-05-21 12:30:35 +03:00
Toutsu 35548a03cb test(discord): update version assertions to 2.6.0
PR Checks / test-and-build (pull_request) Successful in 6m27s
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 12:36:05 +03:00
Toutsu 39132be4e8 feat(discord): enable session join leave buttons
PR Checks / test-and-build (pull_request) Successful in 6m6s
Move neutral join/leave handlers into GmRelay.Shared so Telegram and Discord share capacity, waitlist, duplicate-click, and schedule-update behavior.

Add Discord component routing for join_session and leave_session buttons with deferred ephemeral replies and serialized schedule message updates.

Bump version to 2.5.0 and update Discord docs.

Refs #29
2026-05-19 14:13:48 +03:00
Toutsu d55003a2a9 feat(discord): improve UX and add source-level tests for /newsession
PR Checks / test-and-build (pull_request) Successful in 5m59s
- DiscordNewSessionCommand: on success, renders session details via
  DiscordSessionBatchRenderer.Render() with embeds and action rows.
- DiscordNewSessionCommand: uses Discord emoji shortcodes for error
  and success messages (, , 💥).
- DiscordNewSessionHandlerTests: added 7 source-level structural tests
  verifying Dapper usage, NpgsqlDataSource, permission checks,
  platform neutrality, transaction safety, CancellationToken usage,
  and embed rendering in the command.

Refs issue #28

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 12:36:17 +03:00
Toutsu daa59335cc fix(discord): resolve permission checking for /newsession command
- DiscordPermissionChecker: removed dead-code userRoles overload;
  now only uses resolvedPermissions bitflag (Administrator = 0x8).
- DiscordNewSessionCommand: computes resolved permissions from guild
  user roles via Context.Guild.Users[Id].RoleIds + guild.Roles.
- DiscordNewSessionHandler: updated signature to accept ulong
  resolvedPermissions instead of unused userRoles.
- Added ILogger to command for diagnostics on unexpected errors.
- Added test: regular user with ManageServer (but not Admin) is rejected.

Refs issue #28

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 12:30:25 +03:00
Toutsu 474e7f62f7 chore: bump version to 2.4.0
Synchronized across Directory.Build.props, compose.yaml, deploy.yml,
NavMenu.razor, and project structure tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 11:36:28 +03:00
Toutsu 8666b8984e feat: register Discord session handlers and permission checker in DI
Task 5: DI wiring for DiscordNewSessionHandler, DiscordListSessionsHandler,
DiscordPermissionChecker, and DiscordPlatformMessenger.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 11:33:33 +03:00
Toutsu d373ff49ba feat(discord): add DiscordPlatformMessenger IPlatformMessenger implementation
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 11:22:44 +03:00
Toutsu 95aad3a2f6 feat(discord): add /newsession slash command and handler
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 11:17:07 +03:00
Toutsu 76456cc28a feat(discord): add /listsessions slash command and handler
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 11:09:45 +03:00
Toutsu ac8f03ecc9 feat(discord): add DiscordPermissionChecker for session management rights
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-19 10:51:32 +03:00
Toutsu 5dddf99288 chore: bump version to 2.3.0
PR Checks / test-and-build (pull_request) Successful in 5m34s
Synchronized across Directory.Build.props, compose.yaml, deploy.yml, NavMenu.razor, and DiscordProjectStructureTests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 18:08:12 +03:00
Toutsu 05ca8061e9 feat: add Discord NetCord gateway worker
PR Checks / test-and-build (pull_request) Successful in 5m46s
Add a separate GmRelay.DiscordBot worker using NetCord Gateway with startup token validation, PostgreSQL datasource registration, slash-command setup, component interaction service registration, and lifecycle logging.

Wire the Discord service through Aspire AppHost, Docker Compose, PR checks, deploy image build/push/scan/pull steps, README docs, and synchronized version 2.2.0.

Add TDD coverage for project isolation, token validation, startup wiring, runtime wiring, and version synchronization.

Bump version -> 2.2.0
2026-05-18 16:04:31 +03:00