From a63e3bef1e7369c3539a3a5960be99065f9c51d5 Mon Sep 17 00:00:00 2001 From: Toutsu Date: Thu, 28 May 2026 17:00:33 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20=D1=84=D0=B8=D0=BD=D0=B0=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20=D0=B4=D0=BB=D1=8F=20issue=20#?= =?UTF-8?q?39?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- README.md | 2 +- .../CreateSession/CreateSessionHandler.cs | 7 ++- .../Components/Pages/PublicSession.razor | 57 ++++++++++++++++++- .../Discord/DiscordProjectStructureTests.cs | 14 ++--- 4 files changed, 68 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8722327..25761c5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire. -**Текущая версия:** `v3.3.0`. +**Текущая версия:** `v3.4.0`. --- diff --git a/src/GmRelay.Shared/Features/Sessions/CreateSession/CreateSessionHandler.cs b/src/GmRelay.Shared/Features/Sessions/CreateSession/CreateSessionHandler.cs index a8c551f..b4bedb5 100644 --- a/src/GmRelay.Shared/Features/Sessions/CreateSession/CreateSessionHandler.cs +++ b/src/GmRelay.Shared/Features/Sessions/CreateSession/CreateSessionHandler.cs @@ -118,8 +118,8 @@ public sealed class CreateSessionHandler( { var sessionId = await connection.ExecuteScalarAsync( """ - INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, max_players, system, description, format, duration_minutes, is_one_shot) - VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @MaxPlayers, @System, @Description, @Format, @DurationMinutes, @IsOneShot) + INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, max_players, system, description, format, duration_minutes, is_one_shot, cover_image_url) + VALUES (@BatchId, @GroupId, @Title, @Link, @ScheduledAt, @Status, @MaxPlayers, @System, @Description, @Format, @DurationMinutes, @IsOneShot, @CoverImageUrl) RETURNING id; """, new @@ -135,7 +135,8 @@ public sealed class CreateSessionHandler( command.Description, command.Format, DurationMinutes = command.DurationMinutes, - IsOneShot = command.IsOneShot + IsOneShot = command.IsOneShot, + CoverImageUrl = command.ImageReference }, transaction); diff --git a/src/GmRelay.Web/Components/Pages/PublicSession.razor b/src/GmRelay.Web/Components/Pages/PublicSession.razor index 0fc2fae..b96863d 100644 --- a/src/GmRelay.Web/Components/Pages/PublicSession.razor +++ b/src/GmRelay.Web/Components/Pages/PublicSession.razor @@ -2,7 +2,9 @@ @layout PublicLayout @inject ISessionStore SessionStore @inject NavigationManager Navigation +@inject AuthenticationStateProvider AuthStateProvider @using GmRelay.Shared.Features.Showcase +@using GmRelay.Web.Services @PageTitleText @@ -87,6 +89,13 @@ else if (session is not null) } + @if (registrationResult is not null) + { +
+

@registrationResult

+
+ } +
@if (!string.IsNullOrWhiteSpace(session.GroupSlug)) { @@ -95,7 +104,14 @@ else if (session is not null) Ссылка на сессию @if (session.AllowDirectRegistration) { - Записаться + @if (isAuthenticated) + { + + } + else + { + Войти, чтобы записаться + } }
@@ -106,6 +122,8 @@ else if (session is not null) private ShowcaseSessionDto? session; private bool loaded; + private bool isAuthenticated; + private string? registrationResult; private string PageTitleText => session is null ? "Публичная сессия — GM-Relay" : $"{session.Title} — GM-Relay"; @@ -114,10 +132,47 @@ else if (session is not null) protected override async Task OnParametersSetAsync() { loaded = false; + registrationResult = null; session = await SessionStore.GetShowcaseSessionAsync(SessionId); + + var authState = await AuthStateProvider.GetAuthenticationStateAsync(); + isAuthenticated = authState.User.Identity?.IsAuthenticated ?? false; + + if (session is not null && Navigation.Uri.Contains("register=1") && session.AllowDirectRegistration) + { + if (isAuthenticated && authState.User.TryGetPlatformIdentity(out var platform, out var externalUserId)) + { + var success = await SessionStore.RegisterFromShowcaseAsync(SessionId, platform, externalUserId, authState.User.Identity?.Name ?? "Игрок"); + registrationResult = success + ? "Вы успешно записались на игру!" + : "Не удалось записаться. Возможно, места закончились или вы уже зарегистрированы."; + } + else if (!isAuthenticated) + { + Navigation.NavigateTo($"/login?returnUrl={Uri.EscapeDataString($"/s/{SessionId}")}"); + return; + } + } + loaded = true; } + private async Task RegisterAsync() + { + var authState = await AuthStateProvider.GetAuthenticationStateAsync(); + if (authState.User.TryGetPlatformIdentity(out var platform, out var externalUserId)) + { + var success = await SessionStore.RegisterFromShowcaseAsync(SessionId, platform, externalUserId, authState.User.Identity?.Name ?? "Игрок"); + registrationResult = success + ? "Вы успешно записались на игру!" + : "Не удалось записаться. Возможно, места закончились или вы уже зарегистрированы."; + } + } + + private string GetLoginUrl() => $"/login?returnUrl={Uri.EscapeDataString($"/s/{SessionId}?register=1")}"; + + private string GetRegistrationResultClass() => registrationResult?.StartsWith("Вы успешно") == true ? "status-success-bg" : "status-warning-bg"; + private static string FormatSeats(ShowcaseSessionDto session) { var seats = session.MaxPlayers.HasValue diff --git a/tests/GmRelay.Bot.Tests/Discord/DiscordProjectStructureTests.cs b/tests/GmRelay.Bot.Tests/Discord/DiscordProjectStructureTests.cs index 2a01a82..0ee8503 100644 --- a/tests/GmRelay.Bot.Tests/Discord/DiscordProjectStructureTests.cs +++ b/tests/GmRelay.Bot.Tests/Discord/DiscordProjectStructureTests.cs @@ -62,7 +62,7 @@ public sealed class DiscordProjectStructureTests var prChecks = File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "pr-checks.yml")); var deploy = File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml")); - Assert.Contains("gmrelay-discord-bot:3.3.0", compose); + Assert.Contains("gmrelay-discord-bot:3.4.0", compose); Assert.Contains("Discord__Token=${DISCORD_BOT_TOKEN:?Set DISCORD_BOT_TOKEN in .env}", compose); Assert.Contains("src/GmRelay.DiscordBot/Dockerfile", deploy); Assert.Contains("DISCORD_BOT_TOKEN", deploy); @@ -76,13 +76,13 @@ public sealed class DiscordProjectStructureTests { var repoRoot = GetRepoRoot(); - Assert.Contains("3.3.0", File.ReadAllText(Path.Combine(repoRoot, "Directory.Build.props"))); - Assert.Contains("VERSION: 3.3.0", File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml"))); - Assert.Contains("gmrelay-bot:3.3.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml"))); - Assert.Contains("gmrelay-web:3.3.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml"))); - Assert.Contains("gmrelay-discord-bot:3.3.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml"))); + Assert.Contains("3.4.0", File.ReadAllText(Path.Combine(repoRoot, "Directory.Build.props"))); + Assert.Contains("VERSION: 3.4.0", File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml"))); + Assert.Contains("gmrelay-bot:3.4.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml"))); + Assert.Contains("gmrelay-web:3.4.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml"))); + Assert.Contains("gmrelay-discord-bot:3.4.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml"))); Assert.Contains( - "v3.3.0", + "v3.4.0", File.ReadAllText(Path.Combine(repoRoot, "src", "GmRelay.Web", "Components", "Layout", "NavMenu.razor"))); }