feat(web): dashboard session deletion and E2E coverage for issue #150
- Add DeleteSessionAsync to ISessionStore/SessionService (unpublish portfolio card, remove bot-created empty forum topic, update batch message). - Add DeleteSessionForCurrentUserAsync to AuthorizedSessionService with audit log. - Add delete button + confirmation dialog to GroupDetails.razor. - Extend dashboard Playwright tests with edit persistence and delete verification. - Update AuthorizedSessionServiceTests with delete authorization coverage. - Mark issue #150 as done in tests/e2e/README.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -129,6 +129,14 @@ internal sealed record WebBatchSessionRow(
|
||||
string? CoverImageUrl = null);
|
||||
internal sealed record WebTemplateGroupDto(long TelegramChatId);
|
||||
internal sealed record WebTemplateTopicDestination(int? MessageThreadId, bool TopicCreatedByBot);
|
||||
internal sealed record WebDeleteSessionInfo(
|
||||
Guid Id,
|
||||
string Title,
|
||||
Guid BatchId,
|
||||
long TelegramChatId,
|
||||
int? BatchMessageId,
|
||||
int? ThreadId,
|
||||
bool TopicCreatedByBot);
|
||||
internal sealed record WebPublicGroupRow(
|
||||
Guid GroupId,
|
||||
string Name,
|
||||
@@ -1086,6 +1094,95 @@ public sealed class SessionService(
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> DeleteSessionAsync(Guid sessionId, Guid groupId)
|
||||
{
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
await using var transaction = await conn.BeginTransactionAsync();
|
||||
|
||||
var session = await conn.QuerySingleOrDefaultAsync<WebDeleteSessionInfo>(
|
||||
"""
|
||||
SELECT s.id AS Id,
|
||||
s.title AS Title,
|
||||
s.batch_id AS BatchId,
|
||||
g.external_group_id::BIGINT AS TelegramChatId,
|
||||
s.batch_message_id AS BatchMessageId,
|
||||
s.thread_id AS ThreadId,
|
||||
s.topic_created_by_bot AS TopicCreatedByBot
|
||||
FROM sessions s
|
||||
JOIN game_groups g ON g.id = s.group_id
|
||||
WHERE s.id = @SessionId AND s.group_id = @GroupId
|
||||
FOR UPDATE
|
||||
""",
|
||||
new { SessionId = sessionId, GroupId = groupId },
|
||||
transaction);
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
throw new SessionAccessDeniedException(sessionId, "0");
|
||||
}
|
||||
|
||||
await conn.ExecuteAsync(
|
||||
"""
|
||||
UPDATE portfolio_games pg
|
||||
SET is_public = false,
|
||||
updated_at = now()
|
||||
FROM portfolio_game_sessions pgs
|
||||
WHERE pgs.portfolio_game_id = pg.id
|
||||
AND pgs.session_id = @SessionId
|
||||
AND pg.is_public = true
|
||||
""",
|
||||
new { SessionId = sessionId },
|
||||
transaction);
|
||||
|
||||
await conn.ExecuteAsync(
|
||||
"DELETE FROM sessions WHERE id = @Id AND group_id = @GroupId",
|
||||
new { Id = sessionId, GroupId = groupId },
|
||||
transaction);
|
||||
|
||||
var remainingInTopic = session.ThreadId.HasValue
|
||||
? await conn.ExecuteScalarAsync<int>(
|
||||
"""
|
||||
SELECT COUNT(*)
|
||||
FROM sessions
|
||||
WHERE group_id = @GroupId
|
||||
AND thread_id = @ThreadId
|
||||
""",
|
||||
new { GroupId = groupId, ThreadId = session.ThreadId.Value },
|
||||
transaction)
|
||||
: 0;
|
||||
|
||||
await transaction.CommitAsync();
|
||||
|
||||
if (session.ThreadId.HasValue && session.TopicCreatedByBot && remainingInTopic == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await bot.DeleteForumTopic(
|
||||
chatId: session.TelegramChatId,
|
||||
messageThreadId: session.ThreadId.Value);
|
||||
logger.LogInformation(
|
||||
"Deleted forum topic {ThreadId} for group {GroupId} as no sessions remained.",
|
||||
session.ThreadId.Value,
|
||||
groupId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(
|
||||
ex,
|
||||
"Failed to delete forum topic {ThreadId} for group {GroupId}",
|
||||
session.ThreadId.Value,
|
||||
groupId);
|
||||
}
|
||||
}
|
||||
|
||||
if (session.BatchMessageId.HasValue)
|
||||
{
|
||||
await TryUpdateBatchMessageAsync(session.BatchId, session.TelegramChatId, session.BatchMessageId.Value, session.Title);
|
||||
}
|
||||
|
||||
return session.Title;
|
||||
}
|
||||
|
||||
public async Task PromoteWaitlistedPlayerAsync(Guid sessionId, Guid groupId)
|
||||
{
|
||||
await using var conn = await dataSource.OpenConnectionAsync();
|
||||
|
||||
Reference in New Issue
Block a user