diff --git a/src/GmRelay.Bot/Features/Sessions/ListSessions/SessionListMessageRenderer.cs b/src/GmRelay.Bot/Features/Sessions/ListSessions/SessionListMessageRenderer.cs index de3fe78..48d892b 100644 --- a/src/GmRelay.Bot/Features/Sessions/ListSessions/SessionListMessageRenderer.cs +++ b/src/GmRelay.Bot/Features/Sessions/ListSessions/SessionListMessageRenderer.cs @@ -23,11 +23,18 @@ internal static class SessionListMessageRenderer public static IReadOnlyList RenderActions(IReadOnlyList sessions) { - if (sessions.Count == 0 || !sessions.First().CanManage) + if (sessions.Count == 0) { return []; } + return sessions.First().CanManage + ? RenderManagerActions(sessions) + : RenderPlayerActions(sessions); + } + + private static IReadOnlyList RenderManagerActions(IReadOnlyList sessions) + { var actions = new List(); foreach (var session in sessions) @@ -60,4 +67,31 @@ internal static class SessionListMessageRenderer return actions; } + + private static IReadOnlyList RenderPlayerActions(IReadOnlyList sessions) + { + var actions = new List(); + + foreach (var session in sessions) + { + var dateTitle = session.ScheduledAt.FormatMoscowShort(); + + if (session.IsUserActive || session.IsUserWaitlisted) + { + actions.Add(new PlatformMessageAction( + $"leave_session:{session.Id}", + session.IsUserWaitlisted ? $"✖️ Выйти из ожидания {dateTitle}" : $"✖️ Выйти {dateTitle}", + $"leave_session:{session.Id}")); + } + else + { + actions.Add(new PlatformMessageAction( + $"join_session:{session.Id}", + $"✅ Записаться {dateTitle}", + $"join_session:{session.Id}")); + } + } + + return actions; + } } diff --git a/src/GmRelay.Shared/Features/Sessions/ListSessions/ListSessionsHandler.cs b/src/GmRelay.Shared/Features/Sessions/ListSessions/ListSessionsHandler.cs index 25d047a..38fb65e 100644 --- a/src/GmRelay.Shared/Features/Sessions/ListSessions/ListSessionsHandler.cs +++ b/src/GmRelay.Shared/Features/Sessions/ListSessions/ListSessionsHandler.cs @@ -5,7 +5,17 @@ using Npgsql; namespace GmRelay.Shared.Features.Sessions.ListSessions; -public sealed record SessionListItemDto(Guid Id, string Title, DateTime ScheduledAt, string Status, int? MaxPlayers, int PlayerCount, int WaitlistCount, bool CanManage); +public sealed record SessionListItemDto( + Guid Id, + string Title, + DateTime ScheduledAt, + string Status, + int? MaxPlayers, + int PlayerCount, + int WaitlistCount, + bool CanManage, + bool IsUserActive, + bool IsUserWaitlisted); public sealed record SessionListResult( IReadOnlyList Sessions, @@ -29,7 +39,27 @@ public sealed class ListSessionsHandler( WHERE gm.group_id = s.group_id AND manager_player.platform = @Platform AND manager_player.external_user_id = @ExternalUserId - ) AS CanManage + ) AS CanManage, + EXISTS ( + SELECT 1 + FROM session_participants user_sp + JOIN players user_p ON user_p.id = user_sp.player_id + WHERE user_sp.session_id = s.id + AND user_sp.is_gm = false + AND user_sp.registration_status = @Active + AND user_p.platform = @Platform + AND user_p.external_user_id = @ExternalUserId + ) AS IsUserActive, + EXISTS ( + SELECT 1 + FROM session_participants user_sp + JOIN players user_p ON user_p.id = user_sp.player_id + WHERE user_sp.session_id = s.id + AND user_sp.is_gm = false + AND user_sp.registration_status = @Waitlisted + AND user_p.platform = @Platform + AND user_p.external_user_id = @ExternalUserId + ) AS IsUserWaitlisted FROM sessions s JOIN game_groups g ON s.group_id = g.id LEFT JOIN session_participants sp ON s.id = sp.session_id diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/ListSessions/SessionListMessageRendererTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/ListSessions/SessionListMessageRendererTests.cs index cca7d80..e4368f0 100644 --- a/tests/GmRelay.Bot.Tests/Features/Sessions/ListSessions/SessionListMessageRendererTests.cs +++ b/tests/GmRelay.Bot.Tests/Features/Sessions/ListSessions/SessionListMessageRendererTests.cs @@ -20,7 +20,9 @@ public sealed class SessionListMessageRendererTests 4, 3, 1, - true) + true, + false, + false) }; var text = SessionListMessageRenderer.RenderText(sessions); @@ -41,22 +43,83 @@ public sealed class SessionListMessageRendererTests } [Fact] - public void Render_ShouldHideManagerActions_WhenUserCannotManage() + public void Render_ShouldIncludeJoinAction_WhenPlayerIsNotRegistered() { + var sessionId = Guid.NewGuid(); var sessions = new[] { new SessionListItemDto( - Guid.NewGuid(), + sessionId, + "Ravenloft", + new DateTime(2026, 5, 7, 16, 30, 0, DateTimeKind.Utc), + SessionStatus.Planned, + 4, + 3, + 0, + false, + false, + false) + }; + + var actions = SessionListMessageRenderer.RenderActions(sessions); + var shortDate = new DateTime(2026, 5, 7, 16, 30, 0, DateTimeKind.Utc).FormatMoscowShort(); + + Assert.Single(actions); + Assert.Contains(actions, a => a.Payload == $"join_session:{sessionId}"); + Assert.Contains(actions, a => a.Label == $"✅ Записаться {shortDate}"); + } + + [Fact] + public void Render_ShouldIncludeLeaveAction_WhenPlayerIsActive() + { + var sessionId = Guid.NewGuid(); + var sessions = new[] + { + new SessionListItemDto( + sessionId, + "Ravenloft", + new DateTime(2026, 5, 7, 16, 30, 0, DateTimeKind.Utc), + SessionStatus.Planned, + 4, + 3, + 0, + false, + true, + false) + }; + + var actions = SessionListMessageRenderer.RenderActions(sessions); + var shortDate = new DateTime(2026, 5, 7, 16, 30, 0, DateTimeKind.Utc).FormatMoscowShort(); + + Assert.Single(actions); + Assert.Contains(actions, a => a.Payload == $"leave_session:{sessionId}"); + Assert.Contains(actions, a => a.Label == $"✖️ Выйти {shortDate}"); + } + + [Fact] + public void Render_ShouldIncludeLeaveWaitlistAction_WhenPlayerIsWaitlisted() + { + var sessionId = Guid.NewGuid(); + var sessions = new[] + { + new SessionListItemDto( + sessionId, "Ravenloft", new DateTime(2026, 5, 7, 16, 30, 0, DateTimeKind.Utc), SessionStatus.Planned, 4, 3, 1, - false) + false, + false, + true) }; var actions = SessionListMessageRenderer.RenderActions(sessions); - Assert.Empty(actions); + var shortDate = new DateTime(2026, 5, 7, 16, 30, 0, DateTimeKind.Utc).FormatMoscowShort(); + + Assert.Single(actions); + Assert.Contains(actions, a => a.Payload == $"leave_session:{sessionId}"); + Assert.Contains(actions, a => a.Label == $"✖️ Выйти из ожидания {shortDate}"); } }