164 lines
6.1 KiB
C#
164 lines
6.1 KiB
C#
using Dapper;
|
|
using GmRelay.Shared.Domain;
|
|
using GmRelay.Shared.Features.Sessions.RescheduleSession;
|
|
using GmRelay.Shared.Platform;
|
|
using Npgsql;
|
|
using Telegram.Bot;
|
|
|
|
namespace GmRelay.Bot.Features.Sessions.RescheduleSession;
|
|
|
|
public sealed record HandleRescheduleVoteCommand(
|
|
Guid OptionId,
|
|
long TelegramUserId,
|
|
string CallbackQueryId,
|
|
long ChatId,
|
|
int MessageId);
|
|
|
|
public sealed class HandleRescheduleVoteHandler(
|
|
NpgsqlDataSource dataSource,
|
|
ITelegramBotClient bot,
|
|
IPlatformMessenger messenger,
|
|
ILogger<HandleRescheduleVoteHandler> logger)
|
|
{
|
|
public async Task HandleAsync(HandleRescheduleVoteCommand command, CancellationToken ct)
|
|
{
|
|
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
|
await using var transaction = await connection.BeginTransactionAsync(ct);
|
|
|
|
var proposal = await connection.QuerySingleOrDefaultAsync<VoteProposalDto>(
|
|
"""
|
|
SELECT rp.id AS Id,
|
|
rp.session_id AS SessionId,
|
|
rp.voting_deadline_at AS VotingDeadlineAt,
|
|
s.title AS Title,
|
|
s.scheduled_at AS CurrentScheduledAt
|
|
FROM reschedule_options ro
|
|
JOIN reschedule_proposals rp ON rp.id = ro.proposal_id
|
|
JOIN sessions s ON s.id = rp.session_id
|
|
WHERE ro.id = @OptionId AND rp.status = 'Voting'
|
|
""",
|
|
new { command.OptionId },
|
|
transaction);
|
|
|
|
if (proposal is null)
|
|
{
|
|
await AnswerAsync(command.CallbackQueryId, "Голосование уже завершено или не найдено.", ct);
|
|
return;
|
|
}
|
|
|
|
if (proposal.VotingDeadlineAt <= DateTimeOffset.UtcNow)
|
|
{
|
|
await AnswerAsync(command.CallbackQueryId, "Дедлайн уже прошёл. Результаты скоро будут применены.", ct, showAlert: true);
|
|
return;
|
|
}
|
|
|
|
var playerId = await connection.ExecuteScalarAsync<Guid?>(
|
|
"""
|
|
SELECT p.id
|
|
FROM session_participants sp
|
|
JOIN players p ON p.id = sp.player_id
|
|
WHERE sp.session_id = @SessionId
|
|
AND p.telegram_id = @TelegramUserId
|
|
AND sp.is_gm = false
|
|
AND sp.registration_status = @Active
|
|
""",
|
|
new { proposal.SessionId, command.TelegramUserId, Active = ParticipantRegistrationStatus.Active },
|
|
transaction);
|
|
|
|
if (playerId is null)
|
|
{
|
|
await AnswerAsync(command.CallbackQueryId, "Вы не являетесь участником этой сессии.", ct);
|
|
return;
|
|
}
|
|
|
|
await connection.ExecuteAsync(
|
|
"""
|
|
INSERT INTO reschedule_option_votes (proposal_id, player_id, option_id)
|
|
VALUES (@ProposalId, @PlayerId, @OptionId)
|
|
ON CONFLICT (proposal_id, player_id) DO UPDATE
|
|
SET option_id = EXCLUDED.option_id,
|
|
voted_at = now()
|
|
""",
|
|
new
|
|
{
|
|
ProposalId = proposal.Id,
|
|
PlayerId = playerId.Value,
|
|
command.OptionId
|
|
},
|
|
transaction);
|
|
|
|
var participants = (await connection.QueryAsync<VoteParticipantDto>(
|
|
"""
|
|
SELECT p.id AS PlayerId,
|
|
p.display_name AS DisplayName,
|
|
p.telegram_username AS TelegramUsername,
|
|
p.telegram_id AS TelegramId
|
|
FROM session_participants sp
|
|
JOIN players p ON p.id = sp.player_id
|
|
WHERE sp.session_id = @SessionId
|
|
AND sp.is_gm = false
|
|
AND sp.registration_status = @Active
|
|
ORDER BY p.display_name
|
|
""",
|
|
new { proposal.SessionId, Active = ParticipantRegistrationStatus.Active },
|
|
transaction)).ToList();
|
|
|
|
var options = (await connection.QueryAsync<RescheduleOptionDto>(
|
|
"""
|
|
SELECT id AS OptionId,
|
|
display_order AS DisplayOrder,
|
|
proposed_at AS ProposedAt
|
|
FROM reschedule_options
|
|
WHERE proposal_id = @ProposalId
|
|
ORDER BY display_order
|
|
""",
|
|
new { ProposalId = proposal.Id },
|
|
transaction)).ToList();
|
|
|
|
var votes = (await connection.QueryAsync<RescheduleOptionVoteDto>(
|
|
"""
|
|
SELECT rov.option_id AS OptionId,
|
|
p.id AS PlayerId,
|
|
p.display_name AS DisplayName,
|
|
p.telegram_username AS TelegramUsername
|
|
FROM reschedule_option_votes rov
|
|
JOIN players p ON p.id = rov.player_id
|
|
WHERE rov.proposal_id = @ProposalId
|
|
ORDER BY rov.voted_at, p.display_name
|
|
""",
|
|
new { ProposalId = proposal.Id },
|
|
transaction)).ToList();
|
|
|
|
await transaction.CommitAsync(ct);
|
|
|
|
var voteText = HandleRescheduleTimeInputHandler.BuildVotingMessage(
|
|
proposal.Title,
|
|
proposal.CurrentScheduledAt,
|
|
proposal.VotingDeadlineAt,
|
|
options,
|
|
participants,
|
|
votes);
|
|
var keyboard = HandleRescheduleTimeInputHandler.BuildVotingKeyboard(options);
|
|
|
|
try
|
|
{
|
|
await bot.EditMessageText(
|
|
chatId: command.ChatId,
|
|
messageId: command.MessageId,
|
|
text: voteText,
|
|
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html,
|
|
replyMarkup: keyboard,
|
|
cancellationToken: ct);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogWarning(ex, "Failed to update reschedule vote message for proposal {ProposalId}", proposal.Id);
|
|
}
|
|
|
|
await AnswerAsync(command.CallbackQueryId, "Ваш голос учтён. До дедлайна его можно изменить.", ct);
|
|
}
|
|
|
|
private Task AnswerAsync(string callbackQueryId, string text, CancellationToken ct, bool showAlert = false) =>
|
|
messenger.AnswerInteractionAsync(new PlatformInteractionReply(callbackQueryId, text, showAlert), ct);
|
|
}
|