127 Commits

Author SHA1 Message Date
Toutsu 21e29564f6 docs: document portfolio release and bump version to 3.6.0
PR Checks / test-and-build (pull_request) Successful in 8m32s
2026-06-02 16:07:01 +03:00
Claude 401653a4d1 feat(web): publish completed game portfolios 2026-06-02 15:41:43 +03:00
Toutsu e970e94e00 feat(web): add portfolio management UI 2026-06-02 15:21:51 +03:00
Claude 242ff99a83 feat(web): authorize portfolio management and reviews 2026-06-02 15:01:29 +03:00
Toutsu f2c9f34ab4 feat(web): add portfolio persistence 2026-06-02 14:46:57 +03:00
Toutsu e5945288ac feat(web): add local portfolio cover storage 2026-06-02 12:35:00 +03:00
Toutsu 7d1489445e feat(web): define portfolio contracts and validation 2026-06-02 12:21:55 +03:00
Toutsu d54950698a feat(web): redesign profile page to match design system
PR Checks / test-and-build (pull_request) Successful in 12m8s
- Rewrite Profile.razor to use .page-container, .glass-card, .gm-alert,

  .btn-gm, .status-badge, .empty-state and other standard design system classes

- Replace custom unstyled markup with breadcrumb, page-header, skeleton loaders

- Add .identity-list styles to app.css for linked accounts section

- Unify visual language with Home, Templates and GroupDetails pages
2026-05-29 15:15:48 +03:00
Toutsu b52d4000b4 fix(web): restore public game pages
PR Checks / test-and-build (pull_request) Successful in 11m56s
Use the existing group_managers.created_at column when picking owner profile links for public pages.

Bump version -> 3.5.1
2026-05-29 09:27:01 +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 accb3b2405 fix(web): правильный парсинг query string для ?register=1
PR Checks / test-and-build (pull_request) Successful in 12m50s
Заменен Navigation.Uri.Contains() на QueryHelpers.ParseQuery
для корректного определения параметра register без ложных
срабатываний на подстроки (например, register=10).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 17:28:51 +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 9d9aca53df chore: bump version to 3.4.0 2026-05-28 16:43:11 +03:00
Toutsu 4145cacc52 fix(web): add session-description styles for public session detail 2026-05-28 16:18:58 +03:00
Toutsu 6d59737d07 feat(web): update public session detail with showcase fields
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 16:13:35 +03:00
Toutsu 71ffcce06b fix(web): add try/finally, concurrency guard, accessible label, registration link to showcase 2026-05-28 16:07:09 +03:00
Toutsu 72f43dbef2 feat(web): add /showcase catalog page with filters
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 16:00:21 +03:00
Toutsu a5f4a68c6a fix(web): add public-session guards and ON CONFLICT to RegisterFromShowcaseAsync 2026-05-28 15:40:21 +03:00
Toutsu b2497ed877 feat(web): add showcase query and registration methods 2026-05-28 15:27:18 +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 040b0a3cdb refactor: завершить platform migration и удалить deprecated telegram_* scaffolding
PR Checks / test-and-build (pull_request) Failing after 13m15s
- Добавлены миграции V024 (backfill + deprecation comments + calendar_subscriptions platform identity) и V025 (backfill proposed_by_external_user_id)
- Все Bot handlers переведены с telegram_id/chat_id на platform + external_*
- Shared handlers очищены от COALESCE fallback с telegram_* колонками
- DiscordBot очищен от COALESCE fallback
- Web SessionService и CalendarSubscriptionService переведены на external_*
- HandleRsvpHandler: убран legacy UNION с gm_telegram_id, теперь только group_managers
- RescheduleVotingFinalizer: переведен на external_username/external_user_id
- Tests: добавлены asserts для V024/V025
- Версия обновлена до 3.1.0

Bump version → 3.1.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 16:41:15 +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 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 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 d931da37ec fix(discord): use correct slash command context type in AddApplicationCommands
PR Checks / test-and-build (pull_request) Failing after 8m7s
The default AddApplicationCommands() registers ApplicationCommandService<ApplicationCommandContext>,
but our modules inherit ApplicationCommandModule<SlashCommandContext>. Because SlashCommandContext
does not inherit from ApplicationCommandContext in NetCord, AddModules(typeof(Program).Assembly)
failed to discover the modules, so /newsession, /listsessions, /reschedule were never published
to Discord. Only /ping worked because it uses the minimal API route.

Fix: specify AddApplicationCommands<SlashCommandInteraction, SlashCommandContext>() so the
service matches the module context type, allowing module discovery to succeed.

Bump version to 3.0.4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 17:05:51 +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 2a233b2b1e fix: ensure Telegram is always primary in identity links
Deploy Telegram Bot / build-and-push (push) Successful in 5m6s
Deploy Telegram Bot / scan-images (push) Successful in 1m59s
Deploy Telegram Bot / deploy (push) Successful in 29s
When a Discord user linked Telegram via the Telegram Login Widget,
LinkIdentityAsync incorrectly made Discord primary and Telegram
secondary. This broke access to all Telegram groups/sessions because
ResolveEffectivePlayerIdAsync returned the (empty) Discord primary.

- In /auth/telegram callback, swap LinkIdentityAsync args so Telegram
  is always treated as the current (primary) account.
- Add V022 migration to reverse any existing incorrectly-oriented
  player_links where Discord is primary and Telegram is secondary.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 15:19:08 +03:00
Toutsu 5e3028e470 fix: SameSite=Lax for auth cookie + bidirectional identity linking
Deploy Telegram Bot / build-and-push (push) Successful in 4m45s
Deploy Telegram Bot / scan-images (push) Successful in 2m7s
Deploy Telegram Bot / deploy (push) Successful in 28s
- Change cookie auth SameSite from Strict to Lax so Discord OAuth callback
can see existing Telegram auth session and perform linking instead of
creating a new standalone Discord session (root cause of broken linking).
- Add linking logic to /auth/telegram endpoint for Discord→Telegram linking.
- Add Telegram Login Widget in Profile.razor for Discord users.
- Add CookieAuthOptionsTests to verify Lax SameSite configuration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 14:58:25 +03:00
Toutsu 63193310f2 hotfix: fix Blazor circuit crash on Discord link + add missing avatar_url column
Deploy Telegram Bot / build-and-push (push) Successful in 4m53s
Deploy Telegram Bot / scan-images (push) Successful in 1m47s
Deploy Telegram Bot / deploy (push) Successful in 28s
- Replace @onclick button with plain <a href="/auth/discord"> to avoid
circuit disconnect from forceLoad navigation during event handlers.
- Add query param handling (?linked, ?link_error) in Profile.razor for
Discord callback feedback.
- Add V021 migration: ALTER TABLE players ADD COLUMN avatar_url.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 14:39:24 +03:00
Toutsu af37f3a8ec fix: Profile.razor use ISessionStore directly + forceLoad for Discord link
Deploy Telegram Bot / build-and-push (push) Successful in 4m38s
Deploy Telegram Bot / scan-images (push) Successful in 1m41s
Deploy Telegram Bot / deploy (push) Successful in 26s
- Replace HttpClient API calls with direct ISessionStore DI to avoid
  302 redirect from missing auth cookie in Blazor Server interactive mode
- Use NavigationManager.NavigateTo with forceLoad=true for Discord OAuth
  to bypass Blazor circuit navigation and trigger full HTTP request

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 14:20:26 +03:00
Toutsu 9c59240f48 fix: connection leak in UpsertDiscordUserAsync + false conflict in LinkIdentityAsync
PR Checks / test-and-build (pull_request) Successful in 7m25s
- UpsertDiscordUserAsync: restore await using on opened connection
- LinkIdentityAsync: compute effectiveCurrentPrimary before existingLink check
  to prevent false conflict when current user is a secondary identity

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 13:59:41 +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 7a2ed808c4 fix: replace cookie-based Discord OAuth CSRF with server-side state store
Deploy Telegram Bot / build-and-push (push) Successful in 4m19s
Deploy Telegram Bot / scan-images (push) Successful in 1m24s
Deploy Telegram Bot / deploy (push) Successful in 11s
- Replace __DiscordOAuthState cookie (blocked by third-party cookie policies)
  with in-memory DiscordOAuthStateStore singleton
- State is created server-side and validated on callback, eliminating
  cross-site cookie transmission issues entirely
- Removed CryptographicOperations dependency from Program.cs

Bump version → 2.8.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 13:18:23 +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 7e02e86cd6 fix: add Discord OAuth token exchange logging for production diagnostics
PR Checks / test-and-build (pull_request) Failing after 6m20s
- Log status code and response body when Discord /oauth2/token fails
- Helps identify why ExchangeCodeAsync returns null in production

Bump version → 2.8.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 12:46:56 +03:00
Toutsu 66dc53f12f fix(web): address PR review critical issues for Discord OAuth
PR Checks / test-and-build (pull_request) Successful in 6m6s
- Add V019 migration: rename session_audit_log.actor_telegram_id → actor_external_user_id
- Add CSRF protection to Discord OAuth flow (state cookie with HttpOnly/Secure/Strict)
- Add Discord OAuth env vars to compose.yaml, deploy.yml, and .env.example
- Fix SQL COALESCE for nullable telegram_id in GetGroupManagersAsync and GetSessionParticipantsAsync

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 12:07:40 +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 976e204102 feat(web): add Discord login button, platform indicator, and CSS styles
- Discord login button on /login with brand colors
- NavMenu shows user avatar (Discord) and platform label
- CSS: login-divider, login-btn-discord, nav-user-info, nav-user-platform
- NavMenu version bumped to v2.8.0

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 11:12:16 +03:00
Toutsu 9d4256353d feat(web): refactor SessionStore and AuthorizedSessionService to platform-agnostic identity
- ISessionStore: all methods use (platform, external_user_id)
- SessionService: updated SQL queries and added UpsertDiscordUserAsync
- AuthorizedSessionService: resolves identity from HttpContext, no longer accepts telegram_id params
- SessionAccessDeniedException now accepts string externalUserId
- Added ExternalUserId/ExternalUsername to WebGroupManager and WebParticipant

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 11:08:10 +03:00
Toutsu 543fc42a6d feat(web): add platform-agnostic identity extraction from ClaimsPrincipal
- TryGetPlatformIdentity returns (platform, external_user_id)
- TryGetDiscordId for Discord-specific flows
- Backward-compatible fallback for legacy Telegram auth without Platform claim
- GetAvatarUrl helper for Discord avatars

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 11:02:29 +03:00