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>
This commit is contained in:
2026-05-28 17:00:33 +03:00
parent 9d9aca53df
commit a63e3bef1e
4 changed files with 68 additions and 12 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire.
**Текущая версия:** `v3.3.0`.
**Текущая версия:** `v3.4.0`.
---
@@ -118,8 +118,8 @@ public sealed class CreateSessionHandler(
{
var sessionId = await connection.ExecuteScalarAsync<Guid>(
"""
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);
@@ -2,7 +2,9 @@
@layout PublicLayout
@inject ISessionStore SessionStore
@inject NavigationManager Navigation
@inject AuthenticationStateProvider AuthStateProvider
@using GmRelay.Shared.Features.Showcase
@using GmRelay.Web.Services
<PageTitle>@PageTitleText</PageTitle>
@@ -87,6 +89,13 @@ else if (session is not null)
</div>
}
@if (registrationResult is not null)
{
<div class="glass-card @GetRegistrationResultClass()">
<p>@registrationResult</p>
</div>
}
<div class="public-settings-actions">
@if (!string.IsNullOrWhiteSpace(session.GroupSlug))
{
@@ -95,7 +104,14 @@ else if (session is not null)
<a class="btn-gm btn-gm-outline" href="@PublicSessionUrl" target="_blank" rel="noopener noreferrer">Ссылка на сессию</a>
@if (session.AllowDirectRegistration)
{
<a class="btn-gm btn-gm-primary" href="@($"/s/{SessionId}?register=1")">Записаться</a>
@if (isAuthenticated)
{
<button class="btn-gm btn-gm-primary" @onclick="RegisterAsync">Записаться</button>
}
else
{
<a class="btn-gm btn-gm-primary" href="@GetLoginUrl()">Войти, чтобы записаться</a>
}
}
</div>
</article>
@@ -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
@@ -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("<Version>3.3.0</Version>", 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("<Version>3.4.0</Version>", 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")));
}