040b0a3cdb
PR Checks / test-and-build (pull_request) Failing after 13m15s
- Добавлены миграции V024 (backfill + deprecation comments + calendar_subscriptions platform identity) и V025 (backfill proposed_by_external_user_id) - Все Bot handlers переведены с telegram_id/chat_id на platform + external_* - Shared handlers очищены от COALESCE fallback с telegram_* колонками - DiscordBot очищен от COALESCE fallback - Web SessionService и CalendarSubscriptionService переведены на external_* - HandleRsvpHandler: убран legacy UNION с gm_telegram_id, теперь только group_managers - RescheduleVotingFinalizer: переведен на external_username/external_user_id - Tests: добавлены asserts для V024/V025 - Версия обновлена до 3.1.0 Bump version → 3.1.0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
160 lines
6.4 KiB
C#
160 lines
6.4 KiB
C#
using Dapper;
|
|
using Npgsql;
|
|
using Telegram.Bot;
|
|
using GmRelay.Bot.Infrastructure.Telegram;
|
|
using GmRelay.Shared.Domain;
|
|
|
|
namespace GmRelay.Bot.Features.Sessions.ListSessions;
|
|
|
|
public sealed record DeleteSessionCommand(
|
|
Guid SessionId,
|
|
long TelegramUserId,
|
|
string CallbackQueryId,
|
|
long ChatId,
|
|
int MessageId);
|
|
|
|
internal sealed record DeleteSessionInfoDto(
|
|
string Title,
|
|
Guid BatchId,
|
|
Guid GroupId,
|
|
bool CanManage,
|
|
int? ThreadId,
|
|
bool TopicCreatedByBot);
|
|
|
|
public sealed class DeleteSessionHandler(
|
|
NpgsqlDataSource dataSource,
|
|
ITelegramBotClient bot,
|
|
ILogger<DeleteSessionHandler> logger)
|
|
{
|
|
public async Task HandleAsync(DeleteSessionCommand command, CancellationToken ct)
|
|
{
|
|
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
|
await using var transaction = await connection.BeginTransactionAsync(ct);
|
|
|
|
// 1. Fetch session and verify group manager.
|
|
var session = await connection.QuerySingleOrDefaultAsync<DeleteSessionInfoDto>(
|
|
"""
|
|
SELECT s.title AS Title,
|
|
s.batch_id AS BatchId,
|
|
s.group_id AS GroupId,
|
|
s.thread_id AS ThreadId,
|
|
s.topic_created_by_bot AS TopicCreatedByBot,
|
|
EXISTS (
|
|
SELECT 1
|
|
FROM group_managers gm
|
|
JOIN players p ON p.id = gm.player_id
|
|
WHERE gm.group_id = s.group_id
|
|
AND p.platform = 'Telegram'
|
|
AND p.external_user_id = @ExternalUserId
|
|
) AS CanManage
|
|
FROM sessions s
|
|
WHERE s.id = @SessionId
|
|
""",
|
|
new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString() }, transaction);
|
|
|
|
if (session == null)
|
|
{
|
|
await bot.AnswerCallbackQuery(command.CallbackQueryId, "Сессия не найдена.", cancellationToken: ct);
|
|
return;
|
|
}
|
|
|
|
if (!session.CanManage)
|
|
{
|
|
await bot.AnswerCallbackQuery(command.CallbackQueryId, "Только owner или co-GM может удалять сессию.", showAlert: true, cancellationToken: ct);
|
|
return;
|
|
}
|
|
|
|
// 2. Delete session
|
|
await connection.ExecuteAsync("DELETE FROM sessions WHERE id = @Id", new { Id = command.SessionId }, transaction);
|
|
|
|
var remainingInTopic = session.ThreadId.HasValue
|
|
? await connection.ExecuteScalarAsync<int>(
|
|
"""
|
|
SELECT COUNT(*)
|
|
FROM sessions
|
|
WHERE group_id = @GroupId
|
|
AND thread_id = @ThreadId
|
|
""",
|
|
new { session.GroupId, ThreadId = session.ThreadId.Value },
|
|
transaction)
|
|
: 0;
|
|
|
|
await transaction.CommitAsync(ct);
|
|
|
|
// 4. If no sessions are left in a bot-owned forum topic, delete the topic.
|
|
if (session.ThreadId.HasValue &&
|
|
TelegramTopicRouting.ShouldDeleteForumTopic(session.TopicCreatedByBot, remainingInTopic))
|
|
{
|
|
try
|
|
{
|
|
await bot.DeleteForumTopic(command.ChatId, session.ThreadId.Value, cancellationToken: ct);
|
|
logger.LogInformation("Deleted forum topic {ThreadId} for batch {BatchId} as no sessions remained.", session.ThreadId.Value, session.BatchId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogWarning(ex, "Failed to delete forum topic {ThreadId}", session.ThreadId.Value);
|
|
}
|
|
}
|
|
|
|
await bot.AnswerCallbackQuery(command.CallbackQueryId, "Сессия удалена!", cancellationToken: ct);
|
|
|
|
// 5. Update the /listsessions message (we delete the message or edit it to remove the button)
|
|
// A simple way is to re-render the list:
|
|
await using var readConnection = await dataSource.OpenConnectionAsync(ct);
|
|
var sessions = await readConnection.QueryAsync<SessionListItemDto>(
|
|
@"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt, s.status as Status, s.max_players as MaxPlayers,
|
|
COUNT(sp.id) FILTER (WHERE sp.is_gm = false AND sp.registration_status = @Active) as PlayerCount,
|
|
COUNT(sp.id) FILTER (WHERE sp.is_gm = false AND sp.registration_status = @Waitlisted) as WaitlistCount,
|
|
EXISTS (
|
|
SELECT 1
|
|
FROM group_managers gm
|
|
JOIN players manager_player ON manager_player.id = gm.player_id
|
|
WHERE gm.group_id = s.group_id
|
|
AND manager_player.platform = 'Telegram'
|
|
AND manager_player.external_user_id = @ExternalUserId
|
|
) AS CanManage
|
|
FROM sessions s
|
|
JOIN game_groups g ON s.group_id = g.id
|
|
LEFT JOIN session_participants sp ON s.id = sp.session_id
|
|
WHERE g.platform = 'Telegram'
|
|
AND g.external_group_id = @ExternalChatId
|
|
AND s.status != @Cancelled
|
|
AND s.scheduled_at > NOW()
|
|
GROUP BY s.id, s.title, s.scheduled_at, s.status, s.max_players, s.group_id
|
|
ORDER BY s.scheduled_at ASC",
|
|
new
|
|
{
|
|
ExternalChatId = command.ChatId.ToString(),
|
|
ExternalUserId = command.TelegramUserId.ToString(),
|
|
Cancelled = SessionStatus.Cancelled,
|
|
Active = ParticipantRegistrationStatus.Active,
|
|
Waitlisted = ParticipantRegistrationStatus.Waitlisted
|
|
});
|
|
|
|
var sessionsList = sessions.ToList();
|
|
|
|
if (sessionsList.Count == 0)
|
|
{
|
|
try { await bot.EditMessageText(command.ChatId, command.MessageId, "📭 В этой группе нет предстоящих игр.", cancellationToken: ct); } catch { }
|
|
return;
|
|
}
|
|
|
|
var renderResult = SessionListMessageRenderer.Render(sessionsList);
|
|
|
|
try
|
|
{
|
|
await bot.EditMessageText(
|
|
command.ChatId,
|
|
command.MessageId,
|
|
renderResult.Text,
|
|
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
|
|
replyMarkup: renderResult.Markup,
|
|
cancellationToken: ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogWarning(ex, "Failed to edit list sessions message");
|
|
}
|
|
}
|
|
}
|