using Dapper; using Npgsql; using Telegram.Bot; using Telegram.Bot.Types; using Telegram.Bot.Types.ReplyMarkups; using GmRelay.Shared.Domain; using GmRelay.Shared.Rendering; namespace GmRelay.Bot.Features.Sessions.CreateSession; public sealed record JoinSessionCommand( Guid SessionId, long TelegramUserId, string DisplayName, string? TelegramUsername, string CallbackQueryId, long ChatId, int MessageId); // DTOs for AOT compilation internal sealed record JoinSessionBatchDto(Guid BatchId, string Title); public sealed class JoinSessionHandler( NpgsqlDataSource dataSource, ITelegramBotClient bot, ILogger logger) { public async Task HandleAsync(JoinSessionCommand command, CancellationToken ct) { await using var connection = await dataSource.OpenConnectionAsync(ct); await using var transaction = await connection.BeginTransactionAsync(ct); try { // 1. Убеждаемся, что игрок есть в базе var playerId = await connection.ExecuteScalarAsync( @"INSERT INTO players (telegram_id, display_name, telegram_username) VALUES (@TgId, @Name, @Username) ON CONFLICT (telegram_id) DO UPDATE SET display_name = EXCLUDED.display_name, telegram_username = EXCLUDED.telegram_username RETURNING id;", new { TgId = command.TelegramUserId, Name = command.DisplayName, Username = command.TelegramUsername }, transaction); // 2. Добавляем в участники сессии (статус Pending, так как за 24 часа нужно будет финальное подтверждение) var inserted = await connection.ExecuteAsync( @"INSERT INTO session_participants (session_id, player_id, is_gm, rsvp_status) VALUES (@SessionId, @PlayerId, false, 'Pending') ON CONFLICT (session_id, player_id) DO NOTHING;", new { SessionId = command.SessionId, PlayerId = playerId }, transaction); if (inserted == 0) { await transaction.RollbackAsync(ct); await bot.AnswerCallbackQuery(command.CallbackQueryId, "Вы уже записаны!", cancellationToken: ct); return; } // 3. Получаем batch_id по session_id var batchInfo = await connection.QuerySingleAsync( @"SELECT batch_id as BatchId, title as Title FROM sessions WHERE id = @SessionId", new { command.SessionId }, transaction); // Загружаем весь батч для перерисовки var batchSessions = await connection.QueryAsync( @"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", new { BatchId = batchInfo.BatchId }, transaction); var batchParticipants = await connection.QueryAsync( @"SELECT sp.session_id as SessionId, p.display_name as DisplayName, p.telegram_username as TelegramUsername FROM session_participants sp JOIN players p ON sp.player_id = p.id JOIN sessions s ON sp.session_id = s.id WHERE s.batch_id = @BatchId AND sp.is_gm = false ORDER BY sp.responded_at ASC, p.created_at ASC", new { BatchId = batchInfo.BatchId }, transaction); await transaction.CommitAsync(ct); // 4. Перерисовываем сообщение var renderResult = SessionBatchRenderer.Render(batchInfo.Title, batchSessions.ToList(), batchParticipants.ToList()); await bot.EditMessageText( chatId: command.ChatId, messageId: command.MessageId, text: renderResult.Text, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html, replyMarkup: renderResult.Markup, cancellationToken: ct); await bot.AnswerCallbackQuery(command.CallbackQueryId, "Вы успешно записаны!", cancellationToken: ct); } catch (Exception ex) { logger.LogError(ex, "Ошибка при добавлении игрока к сессии"); await transaction.RollbackAsync(ct); await bot.AnswerCallbackQuery(command.CallbackQueryId, "Произошла ошибка при регистрации.", cancellationToken: ct); } } }