8bcd16fbc9
PR Checks / test-and-build (pull_request) Successful in 12m35s
Introduce platform-neutral PlatformKind, PlatformUser, PlatformGroup, and IPlatformMessenger contracts in GmRelay.Shared. Route Telegram session schedule updates, direct notifications, interaction replies, and calendar export through TelegramPlatformMessenger while preserving existing Telegram behavior. Bump version -> 2.0.1
114 lines
4.9 KiB
C#
114 lines
4.9 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.telegram_id = @TelegramUserId
|
|
) AS CanManage
|
|
FROM sessions s
|
|
WHERE s.id = @SessionId AND s.status != @Cancelled
|
|
""",
|
|
new { command.SessionId, command.TelegramUserId, 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, status)
|
|
VALUES (@SessionId, @GmId, 'AwaitingTime')
|
|
""",
|
|
new { command.SessionId, GmId = command.TelegramUserId });
|
|
|
|
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);
|
|
|
|
await messenger.SendGroupMessageAsync(
|
|
TelegramPlatformIds.Group(command.ChatId, command.MessageThreadId),
|
|
$"""
|
|
⏰ Укажите 2-3 варианта времени для сессии «{session.Title}» и дедлайн голосования.
|
|
|
|
Формат:
|
|
<code>25.04.2026 19:30
|
|
26.04.2026 18:00
|
|
Дедлайн: 25.04.2026 12:00</code>
|
|
|
|
Дедлайн должен быть в будущем и раньше первого предложенного времени.
|
|
""",
|
|
ct);
|
|
}
|
|
|
|
private Task AnswerAsync(string callbackQueryId, string text, CancellationToken ct, bool showAlert = false) =>
|
|
messenger.AnswerInteractionAsync(new PlatformInteractionReply(callbackQueryId, text, showAlert), ct);
|
|
}
|