feat(listsessions): add join/leave buttons for players
For non-managers /listsessions now shows player-friendly actions: - ✅ Записаться <date> when not registered - ✖️ Выйти <date> when already active - ✖️ Выйти из ожидания <date> when waitlisted Extend SessionListItemDto and the shared SQL query with IsUserActive and IsUserWaitlisted flags so the renderer can choose the right button. Update tests to cover all three player states.
This commit is contained in:
@@ -23,11 +23,18 @@ internal static class SessionListMessageRenderer
|
||||
|
||||
public static IReadOnlyList<PlatformMessageAction> RenderActions(IReadOnlyList<SessionListItemDto> sessions)
|
||||
{
|
||||
if (sessions.Count == 0 || !sessions.First().CanManage)
|
||||
if (sessions.Count == 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
return sessions.First().CanManage
|
||||
? RenderManagerActions(sessions)
|
||||
: RenderPlayerActions(sessions);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<PlatformMessageAction> RenderManagerActions(IReadOnlyList<SessionListItemDto> sessions)
|
||||
{
|
||||
var actions = new List<PlatformMessageAction>();
|
||||
|
||||
foreach (var session in sessions)
|
||||
@@ -60,4 +67,31 @@ internal static class SessionListMessageRenderer
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private static IReadOnlyList<PlatformMessageAction> RenderPlayerActions(IReadOnlyList<SessionListItemDto> sessions)
|
||||
{
|
||||
var actions = new List<PlatformMessageAction>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SessionListItemDto> 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
|
||||
|
||||
+68
-5
@@ -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}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user