using Dapper; using GmRelay.Shared.Domain; using GmRelay.Shared.Platform; using Npgsql; namespace GmRelay.Shared.Features.Sessions.RescheduleSession; public sealed class HandleRescheduleVoteHandler( NpgsqlDataSource dataSource) { 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( """ 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) { return new HandleRescheduleVoteResult( false, "Голосование уже завершено или не найдено.", null, null, null, default, default, Array.Empty(), Array.Empty(), Array.Empty()); } if (proposal.VotingDeadlineAt <= DateTimeOffset.UtcNow) { return new HandleRescheduleVoteResult( false, "Дедлайн уже прошёл. Результаты скоро будут применены.", null, null, null, default, default, Array.Empty(), Array.Empty(), Array.Empty()); } var playerId = await connection.ExecuteScalarAsync( """ SELECT p.id FROM session_participants sp JOIN players p ON p.id = sp.player_id WHERE sp.session_id = @SessionId AND p.platform = @Platform AND p.external_user_id = @ExternalUserId AND sp.is_gm = false AND sp.registration_status = @Active """, new { proposal.SessionId, Platform = command.User.Platform.ToString(), ExternalUserId = command.User.ExternalUserId, Active = ParticipantRegistrationStatus.Active }, transaction); if (playerId is null) { return new HandleRescheduleVoteResult( false, "Вы не являетесь участником этой сессии.", null, null, null, default, default, Array.Empty(), Array.Empty(), Array.Empty()); } 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( """ SELECT p.id AS PlayerId, p.display_name AS DisplayName, p.external_username AS TelegramUsername, p.external_user_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( """ 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( """ SELECT rov.option_id AS OptionId, p.id AS PlayerId, p.display_name AS DisplayName, p.external_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); return new HandleRescheduleVoteResult( true, "Ваш голос учтён. До дедлайна его можно изменить.", proposal.Id, proposal.SessionId, proposal.Title, proposal.CurrentScheduledAt, proposal.VotingDeadlineAt, participants, options, votes); } }