|
|
|
@@ -19,9 +19,11 @@ public sealed record WebSession(
|
|
|
|
|
long TelegramChatId,
|
|
|
|
|
int? MaxPlayers,
|
|
|
|
|
int ActivePlayerCount,
|
|
|
|
|
int WaitlistedPlayerCount);
|
|
|
|
|
int WaitlistedPlayerCount,
|
|
|
|
|
string NotificationMode = SessionNotificationModeExtensions.GroupAndDirectValue);
|
|
|
|
|
|
|
|
|
|
internal sealed record WebPromotedParticipantDto(Guid ParticipantRowId, string DisplayName);
|
|
|
|
|
internal sealed record WebDirectNotificationRecipient(long TelegramId, string DisplayName);
|
|
|
|
|
internal sealed record WebBatchInfo(
|
|
|
|
|
Guid BatchId,
|
|
|
|
|
Guid GroupId,
|
|
|
|
@@ -29,7 +31,8 @@ internal sealed record WebBatchInfo(
|
|
|
|
|
string JoinLink,
|
|
|
|
|
long TelegramChatId,
|
|
|
|
|
int? BatchMessageId,
|
|
|
|
|
int? ThreadId);
|
|
|
|
|
int? ThreadId,
|
|
|
|
|
string NotificationMode);
|
|
|
|
|
|
|
|
|
|
internal sealed record WebBatchSessionRow(
|
|
|
|
|
Guid Id,
|
|
|
|
@@ -41,7 +44,8 @@ internal sealed record WebBatchSessionRow(
|
|
|
|
|
int? MaxPlayers,
|
|
|
|
|
int? BatchMessageId,
|
|
|
|
|
long TelegramChatId,
|
|
|
|
|
int? ThreadId);
|
|
|
|
|
int? ThreadId,
|
|
|
|
|
string NotificationMode);
|
|
|
|
|
|
|
|
|
|
public sealed class SessionService(
|
|
|
|
|
NpgsqlDataSource dataSource,
|
|
|
|
@@ -73,7 +77,8 @@ public sealed class SessionService(
|
|
|
|
|
g.telegram_chat_id AS TelegramChatId,
|
|
|
|
|
s.max_players AS MaxPlayers,
|
|
|
|
|
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
|
|
|
|
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount
|
|
|
|
|
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
|
|
|
|
s.notification_mode AS NotificationMode
|
|
|
|
|
FROM sessions s
|
|
|
|
|
JOIN game_groups g ON g.id = s.group_id
|
|
|
|
|
LEFT JOIN LATERAL (
|
|
|
|
@@ -109,7 +114,8 @@ public sealed class SessionService(
|
|
|
|
|
g.telegram_chat_id AS TelegramChatId,
|
|
|
|
|
s.max_players AS MaxPlayers,
|
|
|
|
|
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
|
|
|
|
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount
|
|
|
|
|
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
|
|
|
|
s.notification_mode AS NotificationMode
|
|
|
|
|
FROM sessions s
|
|
|
|
|
JOIN game_groups g ON g.id = s.group_id
|
|
|
|
|
LEFT JOIN LATERAL (
|
|
|
|
@@ -146,7 +152,8 @@ public sealed class SessionService(
|
|
|
|
|
(array_agg(s.join_link ORDER BY s.scheduled_at))[1] AS JoinLink,
|
|
|
|
|
MIN(s.scheduled_at) AS FirstScheduledAt,
|
|
|
|
|
MAX(s.scheduled_at) AS LastScheduledAt,
|
|
|
|
|
COUNT(*)::int AS SessionCount
|
|
|
|
|
COUNT(*)::int AS SessionCount,
|
|
|
|
|
(array_agg(s.notification_mode ORDER BY s.scheduled_at))[1] AS NotificationMode
|
|
|
|
|
FROM sessions s
|
|
|
|
|
WHERE s.batch_id = @BatchId
|
|
|
|
|
GROUP BY s.batch_id, s.group_id
|
|
|
|
@@ -165,7 +172,8 @@ public sealed class SessionService(
|
|
|
|
|
g.telegram_chat_id AS TelegramChatId,
|
|
|
|
|
s.max_players AS MaxPlayers,
|
|
|
|
|
0 AS ActivePlayerCount,
|
|
|
|
|
0 AS WaitlistedPlayerCount
|
|
|
|
|
0 AS WaitlistedPlayerCount,
|
|
|
|
|
s.notification_mode AS NotificationMode
|
|
|
|
|
FROM sessions s
|
|
|
|
|
JOIN game_groups g ON g.id = s.group_id
|
|
|
|
|
WHERE s.id = @Id AND s.group_id = @GroupId",
|
|
|
|
@@ -183,6 +191,10 @@ public sealed class SessionService(
|
|
|
|
|
scheduled_at = @ScheduledAt,
|
|
|
|
|
join_link = @JoinLink,
|
|
|
|
|
max_players = @MaxPlayers,
|
|
|
|
|
one_hour_reminder_processed_at = CASE
|
|
|
|
|
WHEN scheduled_at <> @ScheduledAt THEN NULL
|
|
|
|
|
ELSE one_hour_reminder_processed_at
|
|
|
|
|
END,
|
|
|
|
|
updated_at = now()
|
|
|
|
|
WHERE id = @Id AND group_id = @GroupId",
|
|
|
|
|
new
|
|
|
|
@@ -217,6 +229,13 @@ public sealed class SessionService(
|
|
|
|
|
|
|
|
|
|
await bot.SendMessage(oldSession.TelegramChatId, notification, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
|
|
|
|
|
|
|
|
|
var mode = SessionNotificationModeExtensions.FromDatabaseValue(oldSession.NotificationMode);
|
|
|
|
|
if (mode.ShouldSendDirectMessages())
|
|
|
|
|
{
|
|
|
|
|
var recipients = await LoadSessionDirectRecipientsAsync(conn, sessionId);
|
|
|
|
|
await SendDirectNotificationsAsync(recipients, notification, "web-session-updated", sessionId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (oldSession.BatchMessageId.HasValue)
|
|
|
|
|
{
|
|
|
|
|
await TryUpdateBatchMessageAsync(oldSession.BatchId, oldSession.TelegramChatId, oldSession.BatchMessageId.Value, title);
|
|
|
|
@@ -234,7 +253,8 @@ public sealed class SessionService(
|
|
|
|
|
g.telegram_chat_id AS TelegramChatId,
|
|
|
|
|
s.max_players AS MaxPlayers,
|
|
|
|
|
0 AS ActivePlayerCount,
|
|
|
|
|
0 AS WaitlistedPlayerCount
|
|
|
|
|
0 AS WaitlistedPlayerCount,
|
|
|
|
|
s.notification_mode AS NotificationMode
|
|
|
|
|
FROM sessions s
|
|
|
|
|
JOIN game_groups g ON g.id = s.group_id
|
|
|
|
|
WHERE s.id = @SessionId AND s.group_id = @GroupId
|
|
|
|
@@ -363,6 +383,41 @@ public sealed class SessionService(
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task UpdateBatchNotificationModeAsync(Guid batchId, Guid groupId, SessionNotificationMode notificationMode)
|
|
|
|
|
{
|
|
|
|
|
await using var conn = await dataSource.OpenConnectionAsync();
|
|
|
|
|
await using var transaction = await conn.BeginTransactionAsync();
|
|
|
|
|
|
|
|
|
|
var batch = await GetBatchInfoAsync(conn, batchId, groupId, transaction);
|
|
|
|
|
if (batch is null)
|
|
|
|
|
{
|
|
|
|
|
throw new SessionAccessDeniedException(batchId, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var updatedRows = await conn.ExecuteAsync(
|
|
|
|
|
"""
|
|
|
|
|
UPDATE sessions
|
|
|
|
|
SET notification_mode = @NotificationMode,
|
|
|
|
|
updated_at = now()
|
|
|
|
|
WHERE batch_id = @BatchId
|
|
|
|
|
AND group_id = @GroupId
|
|
|
|
|
""",
|
|
|
|
|
new
|
|
|
|
|
{
|
|
|
|
|
BatchId = batchId,
|
|
|
|
|
GroupId = groupId,
|
|
|
|
|
NotificationMode = notificationMode.ToDatabaseValue()
|
|
|
|
|
},
|
|
|
|
|
transaction);
|
|
|
|
|
|
|
|
|
|
if (updatedRows == 0)
|
|
|
|
|
{
|
|
|
|
|
throw new SessionAccessDeniedException(batchId, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await transaction.CommitAsync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task RescheduleBatchAsync(Guid batchId, Guid groupId, DateTime firstScheduledAt, int intervalDays)
|
|
|
|
|
{
|
|
|
|
|
await using var conn = await dataSource.OpenConnectionAsync();
|
|
|
|
@@ -379,7 +434,8 @@ public sealed class SessionService(
|
|
|
|
|
s.max_players AS MaxPlayers,
|
|
|
|
|
s.batch_message_id AS BatchMessageId,
|
|
|
|
|
g.telegram_chat_id AS TelegramChatId,
|
|
|
|
|
s.thread_id AS ThreadId
|
|
|
|
|
s.thread_id AS ThreadId,
|
|
|
|
|
s.notification_mode AS NotificationMode
|
|
|
|
|
FROM sessions s
|
|
|
|
|
JOIN game_groups g ON g.id = s.group_id
|
|
|
|
|
WHERE s.batch_id = @BatchId
|
|
|
|
@@ -406,6 +462,7 @@ public sealed class SessionService(
|
|
|
|
|
"""
|
|
|
|
|
UPDATE sessions
|
|
|
|
|
SET scheduled_at = @ScheduledAt,
|
|
|
|
|
one_hour_reminder_processed_at = NULL,
|
|
|
|
|
updated_at = now()
|
|
|
|
|
WHERE id = @SessionId
|
|
|
|
|
""",
|
|
|
|
@@ -431,6 +488,13 @@ public sealed class SessionService(
|
|
|
|
|
$"↔️ Шаг: <b>{intervalDays} дн.</b>";
|
|
|
|
|
|
|
|
|
|
await bot.SendMessage(firstSession.TelegramChatId, notification, parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
|
|
|
|
|
|
|
|
|
var mode = SessionNotificationModeExtensions.FromDatabaseValue(firstSession.NotificationMode);
|
|
|
|
|
if (mode.ShouldSendDirectMessages())
|
|
|
|
|
{
|
|
|
|
|
var recipients = await LoadBatchDirectRecipientsAsync(conn, batchId);
|
|
|
|
|
await SendDirectNotificationsAsync(recipients, notification, "web-batch-rescheduled", batchId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<WebSessionBatch> CloneBatchAsync(Guid batchId, Guid groupId, BatchCloneInterval interval)
|
|
|
|
@@ -449,7 +513,8 @@ public sealed class SessionService(
|
|
|
|
|
s.max_players AS MaxPlayers,
|
|
|
|
|
s.batch_message_id AS BatchMessageId,
|
|
|
|
|
g.telegram_chat_id AS TelegramChatId,
|
|
|
|
|
s.thread_id AS ThreadId
|
|
|
|
|
s.thread_id AS ThreadId,
|
|
|
|
|
s.notification_mode AS NotificationMode
|
|
|
|
|
FROM sessions s
|
|
|
|
|
JOIN game_groups g ON g.id = s.group_id
|
|
|
|
|
WHERE s.batch_id = @BatchId
|
|
|
|
@@ -477,8 +542,8 @@ public sealed class SessionService(
|
|
|
|
|
var scheduledAt = BatchSchedulePlanner.ShiftForClone(sourceSession.ScheduledAt, interval);
|
|
|
|
|
var sessionId = await conn.ExecuteScalarAsync<Guid>(
|
|
|
|
|
"""
|
|
|
|
|
INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, max_players)
|
|
|
|
|
VALUES (@BatchId, @GroupId, @Title, @JoinLink, @ScheduledAt, @Status, @ThreadId, @MaxPlayers)
|
|
|
|
|
INSERT INTO sessions (batch_id, group_id, title, join_link, scheduled_at, status, thread_id, max_players, notification_mode)
|
|
|
|
|
VALUES (@BatchId, @GroupId, @Title, @JoinLink, @ScheduledAt, @Status, @ThreadId, @MaxPlayers, @NotificationMode)
|
|
|
|
|
RETURNING id
|
|
|
|
|
""",
|
|
|
|
|
new
|
|
|
|
@@ -490,7 +555,8 @@ public sealed class SessionService(
|
|
|
|
|
ScheduledAt = scheduledAt,
|
|
|
|
|
Status = SessionStatus.Planned,
|
|
|
|
|
ThreadId = threadId,
|
|
|
|
|
sourceSession.MaxPlayers
|
|
|
|
|
sourceSession.MaxPlayers,
|
|
|
|
|
sourceSession.NotificationMode
|
|
|
|
|
},
|
|
|
|
|
transaction);
|
|
|
|
|
|
|
|
|
@@ -518,7 +584,71 @@ public sealed class SessionService(
|
|
|
|
|
batchJoinLink,
|
|
|
|
|
renderedSessions.Min(session => session.ScheduledAt),
|
|
|
|
|
renderedSessions.Max(session => session.ScheduledAt),
|
|
|
|
|
renderedSessions.Count);
|
|
|
|
|
renderedSessions.Count,
|
|
|
|
|
sourceSessions[0].NotificationMode);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<WebDirectNotificationRecipient>> LoadSessionDirectRecipientsAsync(
|
|
|
|
|
Npgsql.NpgsqlConnection conn,
|
|
|
|
|
Guid sessionId)
|
|
|
|
|
{
|
|
|
|
|
return (await conn.QueryAsync<WebDirectNotificationRecipient>(
|
|
|
|
|
"""
|
|
|
|
|
SELECT p.telegram_id AS TelegramId,
|
|
|
|
|
p.display_name AS DisplayName
|
|
|
|
|
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
|
|
|
|
|
""",
|
|
|
|
|
new { SessionId = sessionId, Active = ParticipantRegistrationStatus.Active })).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task<List<WebDirectNotificationRecipient>> LoadBatchDirectRecipientsAsync(
|
|
|
|
|
Npgsql.NpgsqlConnection conn,
|
|
|
|
|
Guid batchId)
|
|
|
|
|
{
|
|
|
|
|
return (await conn.QueryAsync<WebDirectNotificationRecipient>(
|
|
|
|
|
"""
|
|
|
|
|
SELECT DISTINCT p.telegram_id AS TelegramId,
|
|
|
|
|
p.display_name AS DisplayName
|
|
|
|
|
FROM session_participants sp
|
|
|
|
|
JOIN players p ON p.id = sp.player_id
|
|
|
|
|
JOIN sessions s ON s.id = sp.session_id
|
|
|
|
|
WHERE s.batch_id = @BatchId
|
|
|
|
|
AND sp.is_gm = false
|
|
|
|
|
AND sp.registration_status = @Active
|
|
|
|
|
""",
|
|
|
|
|
new { BatchId = batchId, Active = ParticipantRegistrationStatus.Active })).ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task SendDirectNotificationsAsync(
|
|
|
|
|
IEnumerable<WebDirectNotificationRecipient> recipients,
|
|
|
|
|
string htmlText,
|
|
|
|
|
string notificationKind,
|
|
|
|
|
Guid entityId)
|
|
|
|
|
{
|
|
|
|
|
foreach (var recipient in recipients)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
await bot.SendMessage(
|
|
|
|
|
chatId: recipient.TelegramId,
|
|
|
|
|
text: htmlText,
|
|
|
|
|
parseMode: Telegram.Bot.Types.Enums.ParseMode.Html);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
logger.LogWarning(
|
|
|
|
|
ex,
|
|
|
|
|
"Failed to send {NotificationKind} DM for entity {EntityId} to player {TelegramId} ({DisplayName})",
|
|
|
|
|
notificationKind,
|
|
|
|
|
entityId,
|
|
|
|
|
recipient.TelegramId,
|
|
|
|
|
recipient.DisplayName);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task TryUpdateBatchMessageAsync(Guid batchId, long chatId, int messageId, string title)
|
|
|
|
@@ -572,7 +702,8 @@ public sealed class SessionService(
|
|
|
|
|
(array_agg(s.join_link ORDER BY s.scheduled_at))[1] AS JoinLink,
|
|
|
|
|
g.telegram_chat_id AS TelegramChatId,
|
|
|
|
|
(array_agg(s.batch_message_id ORDER BY s.scheduled_at))[1] AS BatchMessageId,
|
|
|
|
|
(array_agg(s.thread_id ORDER BY s.scheduled_at))[1] AS ThreadId
|
|
|
|
|
(array_agg(s.thread_id ORDER BY s.scheduled_at))[1] AS ThreadId,
|
|
|
|
|
(array_agg(s.notification_mode ORDER BY s.scheduled_at))[1] AS NotificationMode
|
|
|
|
|
FROM sessions s
|
|
|
|
|
JOIN game_groups g ON g.id = s.group_id
|
|
|
|
|
WHERE s.batch_id = @BatchId
|
|
|
|
|