Files
GmRelayBot/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs
T
Toutsu 040b0a3cdb
PR Checks / test-and-build (pull_request) Failing after 13m15s
refactor: завершить platform migration и удалить deprecated telegram_* scaffolding
- Добавлены миграции 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>
2026-05-26 16:41:15 +03:00

120 lines
5.0 KiB
C#

using Dapper;
using GmRelay.Bot.Infrastructure.Telegram;
using GmRelay.Shared.Domain;
using GmRelay.Shared.Platform;
using Npgsql;
namespace GmRelay.Bot.Features.Sessions.RescheduleSession;
// ── Command ──────────────────────────────────────────────────────────
public sealed record InitiateRescheduleCommand(
Guid SessionId,
long TelegramUserId,
string CallbackQueryId,
long ChatId,
int? MessageThreadId,
int MessageId);
// ── DTOs ─────────────────────────────────────────────────────────────
internal sealed record RescheduleSessionInfoDto(string Title, bool CanManage);
// ── Handler ──────────────────────────────────────────────────────────
/// <summary>
/// Handles the "⏰ Перенести" button press from the batch message.
/// Creates a reschedule proposal in AwaitingTime status and prompts
/// the GM to enter 2-3 new time options and a voting deadline.
/// </summary>
public sealed class InitiateRescheduleHandler(
NpgsqlDataSource dataSource,
IPlatformMessenger messenger,
ILogger<InitiateRescheduleHandler> logger)
{
public async Task HandleAsync(InitiateRescheduleCommand command, CancellationToken ct)
{
await using var connection = await dataSource.OpenConnectionAsync(ct);
// 1. Verify group management access.
var session = await connection.QuerySingleOrDefaultAsync<RescheduleSessionInfoDto>(
"""
SELECT s.title AS Title,
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 AND s.status != @Cancelled
""",
new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString(), Cancelled = SessionStatus.Cancelled });
if (session is null)
{
await AnswerAsync(command.CallbackQueryId, "Сессия не найдена.", ct);
return;
}
if (!session.CanManage)
{
await AnswerAsync(command.CallbackQueryId, "Только owner или co-GM может переносить сессию.", ct, showAlert: true);
return;
}
// 2. Check no active proposal exists
var hasActive = await connection.ExecuteScalarAsync<bool>(
"""
SELECT EXISTS (
SELECT 1 FROM reschedule_proposals
WHERE session_id = @SessionId AND status IN ('AwaitingTime', 'Voting')
)
""",
new { command.SessionId });
if (hasActive)
{
await AnswerAsync(command.CallbackQueryId, "Уже есть активный запрос на перенос этой сессии.", ct, showAlert: true);
return;
}
// 3. Create proposal in AwaitingTime status
await connection.ExecuteAsync(
"""
INSERT INTO reschedule_proposals (session_id, proposed_by_external_user_id, source_platform, status)
VALUES (@SessionId, @ProposedBy, 'Telegram', 'AwaitingTime')
""",
new { command.SessionId, ProposedBy = command.TelegramUserId.ToString() });
logger.LogInformation("Reschedule initiated for session {SessionId} by GM {GmId}", command.SessionId, command.TelegramUserId);
// 4. Prompt GM in chat
await AnswerAsync(command.CallbackQueryId, "Введите 2-3 варианта времени и дедлайн голосования.", ct);
var prompt = string.Join(
"\n",
new[]
{
$"⏰ Укажите 2-3 варианта времени для сессии «{session.Title}» и дедлайн голосования.",
"",
"Формат:",
"<code>25.04.2026 19:30",
"26.04.2026 18:00",
"Дедлайн: 25.04.2026 12:00</code>",
"",
"Дедлайн должен быть в будущем и раньше первого предложенного времени."
});
await messenger.SendGroupMessageAsync(
TelegramPlatformIds.Group(command.ChatId, command.MessageThreadId),
prompt,
ct);
}
private Task AnswerAsync(string callbackQueryId, string text, CancellationToken ct, bool showAlert = false) =>
messenger.AnswerInteractionAsync(new PlatformInteractionReply(callbackQueryId, text, showAlert), ct);
}