refactor: завершить platform migration и удалить deprecated telegram_* scaffolding
PR Checks / test-and-build (pull_request) Failing after 13m15s
PR Checks / test-and-build (pull_request) Failing after 13m15s
- Добавлены миграции 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>
This commit is contained in:
@@ -6,7 +6,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
|
|
||||||
env:
|
env:
|
||||||
VERSION: 3.0.10
|
VERSION: 3.1.0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>3.0.10</Version>
|
<Version>3.1.0</Version>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<LangVersion>preview</LangVersion>
|
<LangVersion>preview</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|||||||
+3
-3
@@ -49,7 +49,7 @@ services:
|
|||||||
crond -f
|
crond -f
|
||||||
|
|
||||||
bot:
|
bot:
|
||||||
image: git.codeanddice.ru/toutsu/gmrelay-bot:3.0.10
|
image: git.codeanddice.ru/toutsu/gmrelay-bot:3.1.0
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
@@ -67,7 +67,7 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
discord:
|
discord:
|
||||||
image: git.codeanddice.ru/toutsu/gmrelay-discord-bot:3.0.10
|
image: git.codeanddice.ru/toutsu/gmrelay-discord-bot:3.1.0
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
@@ -84,7 +84,7 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: git.codeanddice.ru/toutsu/gmrelay-web:3.0.10
|
image: git.codeanddice.ru/toutsu/gmrelay-web:3.1.0
|
||||||
restart: always
|
restart: always
|
||||||
depends_on:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
|
|||||||
@@ -42,12 +42,13 @@ public sealed class CancelSessionHandler(
|
|||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players p ON p.id = gm.player_id
|
JOIN players p ON p.id = gm.player_id
|
||||||
WHERE gm.group_id = s.group_id
|
WHERE gm.group_id = s.group_id
|
||||||
AND p.telegram_id = @TelegramUserId
|
AND p.platform = 'Telegram'
|
||||||
|
AND p.external_user_id = @ExternalUserId
|
||||||
) AS CanManage
|
) AS CanManage
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
WHERE s.id = @SessionId
|
WHERE s.id = @SessionId
|
||||||
""",
|
""",
|
||||||
new { command.SessionId, command.TelegramUserId }, transaction);
|
new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString() }, transaction);
|
||||||
|
|
||||||
if (session == null)
|
if (session == null)
|
||||||
{
|
{
|
||||||
@@ -89,7 +90,7 @@ public sealed class CancelSessionHandler(
|
|||||||
|
|
||||||
var directRecipients = (await connection.QueryAsync<DirectNotificationRecipient>(
|
var directRecipients = (await connection.QueryAsync<DirectNotificationRecipient>(
|
||||||
"""
|
"""
|
||||||
SELECT p.telegram_id AS TelegramId,
|
SELECT p.external_user_id::BIGINT AS TelegramId,
|
||||||
p.display_name AS DisplayName
|
p.display_name AS DisplayName
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON sp.player_id = p.id
|
JOIN players p ON sp.player_id = p.id
|
||||||
|
|||||||
@@ -77,16 +77,15 @@ public sealed class CreateSessionHandler(
|
|||||||
{
|
{
|
||||||
await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
"""
|
"""
|
||||||
INSERT INTO players (telegram_id, display_name, telegram_username, platform, external_user_id, external_username)
|
INSERT INTO players (display_name, platform, external_user_id, external_username)
|
||||||
VALUES (@TgId, @Name, @Username, 'Telegram', @TgId::TEXT, @Username)
|
VALUES (@Name, 'Telegram', @ExternalId, @Username)
|
||||||
ON CONFLICT (telegram_id) DO UPDATE
|
ON CONFLICT (platform, external_user_id)
|
||||||
|
WHERE platform IS NOT NULL AND external_user_id IS NOT NULL
|
||||||
|
DO UPDATE
|
||||||
SET display_name = EXCLUDED.display_name,
|
SET display_name = EXCLUDED.display_name,
|
||||||
telegram_username = EXCLUDED.telegram_username,
|
external_username = EXCLUDED.external_username;
|
||||||
platform = COALESCE(players.platform, 'Telegram'),
|
|
||||||
external_user_id = COALESCE(players.external_user_id, EXCLUDED.telegram_id::TEXT),
|
|
||||||
external_username = COALESCE(players.external_username, EXCLUDED.telegram_username);
|
|
||||||
""",
|
""",
|
||||||
new { TgId = gmId, Name = gmName, Username = gmUsername },
|
new { ExternalId = gmId.ToString(), Name = gmName, Username = gmUsername },
|
||||||
transaction);
|
transaction);
|
||||||
|
|
||||||
var existingGroup = await connection.QuerySingleOrDefaultAsync<SessionCreationGroupAccessDto>(
|
var existingGroup = await connection.QuerySingleOrDefaultAsync<SessionCreationGroupAccessDto>(
|
||||||
@@ -97,12 +96,14 @@ public sealed class CreateSessionHandler(
|
|||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players p ON p.id = gm.player_id
|
JOIN players p ON p.id = gm.player_id
|
||||||
WHERE gm.group_id = g.id
|
WHERE gm.group_id = g.id
|
||||||
AND COALESCE(p.external_user_id, p.telegram_id::TEXT) = @GmId::TEXT
|
AND p.platform = 'Telegram'
|
||||||
|
AND p.external_user_id = @ExternalGmId
|
||||||
) AS CanManage
|
) AS CanManage
|
||||||
FROM game_groups g
|
FROM game_groups g
|
||||||
WHERE COALESCE(g.external_group_id, g.telegram_chat_id::TEXT) = @ChatId::TEXT
|
WHERE g.platform = 'Telegram'
|
||||||
|
AND g.external_group_id = @ExternalChatId
|
||||||
""",
|
""",
|
||||||
new { ChatId = chatId, GmId = gmId },
|
new { ExternalChatId = chatId.ToString(), ExternalGmId = gmId.ToString() },
|
||||||
transaction);
|
transaction);
|
||||||
|
|
||||||
Guid groupId;
|
Guid groupId;
|
||||||
@@ -110,11 +111,11 @@ public sealed class CreateSessionHandler(
|
|||||||
{
|
{
|
||||||
groupId = await connection.ExecuteScalarAsync<Guid>(
|
groupId = await connection.ExecuteScalarAsync<Guid>(
|
||||||
"""
|
"""
|
||||||
INSERT INTO game_groups (telegram_chat_id, name, gm_telegram_id, platform, external_group_id)
|
INSERT INTO game_groups (name, platform, external_group_id)
|
||||||
VALUES (@ChatId, @ChatName, @GmId, 'Telegram', @ChatId::TEXT)
|
VALUES (@ChatName, 'Telegram', @ExternalChatId)
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
""",
|
""",
|
||||||
new { ChatId = chatId, ChatName = chatTitle, GmId = gmId },
|
new { ExternalChatId = chatId.ToString(), ChatName = chatTitle },
|
||||||
transaction);
|
transaction);
|
||||||
|
|
||||||
await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
@@ -122,10 +123,11 @@ public sealed class CreateSessionHandler(
|
|||||||
INSERT INTO group_managers (group_id, player_id, role)
|
INSERT INTO group_managers (group_id, player_id, role)
|
||||||
SELECT @GroupId, p.id, @OwnerRole
|
SELECT @GroupId, p.id, @OwnerRole
|
||||||
FROM players p
|
FROM players p
|
||||||
WHERE COALESCE(p.external_user_id, p.telegram_id::TEXT) = @GmId::TEXT
|
WHERE p.platform = 'Telegram'
|
||||||
|
AND p.external_user_id = @ExternalGmId
|
||||||
ON CONFLICT (group_id, player_id) DO NOTHING
|
ON CONFLICT (group_id, player_id) DO NOTHING
|
||||||
""",
|
""",
|
||||||
new { GroupId = groupId, GmId = gmId, OwnerRole = GroupManagerRoleExtensions.OwnerValue },
|
new { GroupId = groupId, ExternalGmId = gmId.ToString(), OwnerRole = GroupManagerRoleExtensions.OwnerValue },
|
||||||
transaction);
|
transaction);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -41,13 +41,14 @@ public sealed class PromoteWaitlistedPlayerHandler(
|
|||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players p ON p.id = gm.player_id
|
JOIN players p ON p.id = gm.player_id
|
||||||
WHERE gm.group_id = s.group_id
|
WHERE gm.group_id = s.group_id
|
||||||
AND p.telegram_id = @TelegramUserId
|
AND p.platform = 'Telegram'
|
||||||
|
AND p.external_user_id = @ExternalUserId
|
||||||
) AS CanManage
|
) AS CanManage
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
WHERE s.id = @SessionId
|
WHERE s.id = @SessionId
|
||||||
FOR UPDATE
|
FOR UPDATE
|
||||||
""",
|
""",
|
||||||
new { command.SessionId, command.TelegramUserId },
|
new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString() },
|
||||||
transaction);
|
transaction);
|
||||||
|
|
||||||
if (session is null)
|
if (session is null)
|
||||||
@@ -150,7 +151,7 @@ public sealed class PromoteWaitlistedPlayerHandler(
|
|||||||
"""
|
"""
|
||||||
SELECT sp.session_id AS SessionId,
|
SELECT sp.session_id AS SessionId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
sp.registration_status AS RegistrationStatus
|
sp.registration_status AS RegistrationStatus
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON sp.player_id = p.id
|
JOIN players p ON sp.player_id = p.id
|
||||||
|
|||||||
@@ -24,11 +24,12 @@ public sealed class ExportCalendarHandler(
|
|||||||
@"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt"
|
@"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt"
|
||||||
+ " FROM sessions s"
|
+ " FROM sessions s"
|
||||||
+ " JOIN game_groups g ON s.group_id = g.id"
|
+ " JOIN game_groups g ON s.group_id = g.id"
|
||||||
+ " WHERE g.telegram_chat_id = @ChatId"
|
+ " WHERE g.platform = 'Telegram'"
|
||||||
|
+ " AND g.external_group_id = @ExternalChatId"
|
||||||
+ " AND s.status = @Planned"
|
+ " AND s.status = @Planned"
|
||||||
+ " AND s.scheduled_at > NOW()"
|
+ " AND s.scheduled_at > NOW()"
|
||||||
+ " ORDER BY s.scheduled_at ASC",
|
+ " ORDER BY s.scheduled_at ASC",
|
||||||
new { ChatId = message.Chat.Id, Planned = SessionStatus.Planned });
|
new { ExternalChatId = message.Chat.Id.ToString(), Planned = SessionStatus.Planned });
|
||||||
|
|
||||||
var sessionsList = sessions.ToList();
|
var sessionsList = sessions.ToList();
|
||||||
|
|
||||||
@@ -75,13 +76,13 @@ public sealed class ExportCalendarHandler(
|
|||||||
{
|
{
|
||||||
var token = Guid.NewGuid().ToString("N");
|
var token = Guid.NewGuid().ToString("N");
|
||||||
var groupId = await connection.QueryFirstOrDefaultAsync<Guid?>(
|
var groupId = await connection.QueryFirstOrDefaultAsync<Guid?>(
|
||||||
@"SELECT id FROM game_groups WHERE telegram_chat_id = @ChatId",
|
@"SELECT id FROM game_groups WHERE platform = 'Telegram' AND external_group_id = @ExternalChatId",
|
||||||
new { ChatId = message.Chat.Id });
|
new { ExternalChatId = message.Chat.Id.ToString() });
|
||||||
|
|
||||||
await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
@"INSERT INTO calendar_subscriptions (id, token, user_telegram_id, group_id, filter_type, created_at, expires_at)
|
@"INSERT INTO calendar_subscriptions (id, token, user_platform, user_external_id, group_id, filter_type, created_at, expires_at)
|
||||||
VALUES (gen_random_uuid(), @token, @userTelegramId, @groupId, @filterType, now(), NULL)",
|
VALUES (gen_random_uuid(), @token, 'Telegram', @userExternalId, @groupId, @filterType, now(), NULL)",
|
||||||
new { token, userTelegramId = senderId.Value, groupId, filterType = (int)CalendarSubscriptionFilter.SpecificGroup });
|
new { token, userExternalId = senderId.Value.ToString(), groupId, filterType = (int)CalendarSubscriptionFilter.SpecificGroup });
|
||||||
|
|
||||||
subscriptionUrl = $"{baseUrl.TrimEnd('/')}/calendar/{token}.ics";
|
subscriptionUrl = $"{baseUrl.TrimEnd('/')}/calendar/{token}.ics";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,13 @@ public sealed class DeleteSessionHandler(
|
|||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players p ON p.id = gm.player_id
|
JOIN players p ON p.id = gm.player_id
|
||||||
WHERE gm.group_id = s.group_id
|
WHERE gm.group_id = s.group_id
|
||||||
AND p.telegram_id = @TelegramUserId
|
AND p.platform = 'Telegram'
|
||||||
|
AND p.external_user_id = @ExternalUserId
|
||||||
) AS CanManage
|
) AS CanManage
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
WHERE s.id = @SessionId
|
WHERE s.id = @SessionId
|
||||||
""",
|
""",
|
||||||
new { command.SessionId, command.TelegramUserId }, transaction);
|
new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString() }, transaction);
|
||||||
|
|
||||||
if (session == null)
|
if (session == null)
|
||||||
{
|
{
|
||||||
@@ -109,18 +110,22 @@ public sealed class DeleteSessionHandler(
|
|||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players manager_player ON manager_player.id = gm.player_id
|
JOIN players manager_player ON manager_player.id = gm.player_id
|
||||||
WHERE gm.group_id = s.group_id
|
WHERE gm.group_id = s.group_id
|
||||||
AND manager_player.telegram_id = @TelegramUserId
|
AND manager_player.platform = 'Telegram'
|
||||||
|
AND manager_player.external_user_id = @ExternalUserId
|
||||||
) AS CanManage
|
) AS CanManage
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
JOIN game_groups g ON s.group_id = g.id
|
JOIN game_groups g ON s.group_id = g.id
|
||||||
LEFT JOIN session_participants sp ON s.id = sp.session_id
|
LEFT JOIN session_participants sp ON s.id = sp.session_id
|
||||||
WHERE g.telegram_chat_id = @ChatId AND s.status != @Cancelled AND s.scheduled_at > NOW()
|
WHERE g.platform = 'Telegram'
|
||||||
|
AND g.external_group_id = @ExternalChatId
|
||||||
|
AND s.status != @Cancelled
|
||||||
|
AND s.scheduled_at > NOW()
|
||||||
GROUP BY s.id, s.title, s.scheduled_at, s.status, s.max_players, s.group_id
|
GROUP BY s.id, s.title, s.scheduled_at, s.status, s.max_players, s.group_id
|
||||||
ORDER BY s.scheduled_at ASC",
|
ORDER BY s.scheduled_at ASC",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
ChatId = command.ChatId,
|
ExternalChatId = command.ChatId.ToString(),
|
||||||
command.TelegramUserId,
|
ExternalUserId = command.TelegramUserId.ToString(),
|
||||||
Cancelled = SessionStatus.Cancelled,
|
Cancelled = SessionStatus.Cancelled,
|
||||||
Active = ParticipantRegistrationStatus.Active,
|
Active = ParticipantRegistrationStatus.Active,
|
||||||
Waitlisted = ParticipantRegistrationStatus.Waitlisted
|
Waitlisted = ParticipantRegistrationStatus.Waitlisted
|
||||||
|
|||||||
@@ -74,18 +74,22 @@ public sealed class ListSessionsHandler(
|
|||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players manager_player ON manager_player.id = gm.player_id
|
JOIN players manager_player ON manager_player.id = gm.player_id
|
||||||
WHERE gm.group_id = s.group_id
|
WHERE gm.group_id = s.group_id
|
||||||
AND manager_player.telegram_id = @TelegramUserId
|
AND manager_player.platform = 'Telegram'
|
||||||
|
AND manager_player.external_user_id = @ExternalUserId
|
||||||
) AS CanManage
|
) AS CanManage
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
JOIN game_groups g ON s.group_id = g.id
|
JOIN game_groups g ON s.group_id = g.id
|
||||||
LEFT JOIN session_participants sp ON s.id = sp.session_id
|
LEFT JOIN session_participants sp ON s.id = sp.session_id
|
||||||
WHERE g.telegram_chat_id = @ChatId AND s.status != @Cancelled AND s.scheduled_at > NOW()
|
WHERE g.platform = 'Telegram'
|
||||||
|
AND g.external_group_id = @ExternalChatId
|
||||||
|
AND s.status != @Cancelled
|
||||||
|
AND s.scheduled_at > NOW()
|
||||||
GROUP BY s.id, s.title, s.scheduled_at, s.status, s.max_players, s.group_id
|
GROUP BY s.id, s.title, s.scheduled_at, s.status, s.max_players, s.group_id
|
||||||
ORDER BY s.scheduled_at ASC",
|
ORDER BY s.scheduled_at ASC",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
ChatId = message.Chat.Id,
|
ExternalChatId = message.Chat.Id.ToString(),
|
||||||
TelegramUserId = message.From?.Id,
|
ExternalUserId = message.From?.Id.ToString(),
|
||||||
Cancelled = SessionStatus.Cancelled,
|
Cancelled = SessionStatus.Cancelled,
|
||||||
Active = ParticipantRegistrationStatus.Active,
|
Active = ParticipantRegistrationStatus.Active,
|
||||||
Waitlisted = ParticipantRegistrationStatus.Waitlisted
|
Waitlisted = ParticipantRegistrationStatus.Waitlisted
|
||||||
|
|||||||
+10
-8
@@ -53,26 +53,28 @@ public sealed class HandleRescheduleTimeInputHandler(
|
|||||||
"""
|
"""
|
||||||
SELECT rp.id AS Id, rp.session_id AS SessionId, s.title AS Title, s.scheduled_at AS CurrentScheduledAt,
|
SELECT rp.id AS Id, rp.session_id AS SessionId, s.title AS Title, s.scheduled_at AS CurrentScheduledAt,
|
||||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.thread_id AS ThreadId,
|
s.thread_id AS ThreadId,
|
||||||
s.notification_mode AS NotificationMode
|
s.notification_mode AS NotificationMode
|
||||||
FROM reschedule_proposals rp
|
FROM reschedule_proposals rp
|
||||||
JOIN sessions s ON s.id = rp.session_id
|
JOIN sessions s ON s.id = rp.session_id
|
||||||
JOIN game_groups g ON g.id = s.group_id
|
JOIN game_groups g ON g.id = s.group_id
|
||||||
WHERE rp.proposed_by = @GmId
|
WHERE rp.proposed_by_external_user_id = @ExternalGmId
|
||||||
AND rp.status = 'AwaitingTime'
|
AND rp.status = 'AwaitingTime'
|
||||||
AND g.telegram_chat_id = @ChatId
|
AND g.platform = 'Telegram'
|
||||||
|
AND g.external_group_id = @ExternalChatId
|
||||||
AND EXISTS (
|
AND EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players manager_player ON manager_player.id = gm.player_id
|
JOIN players manager_player ON manager_player.id = gm.player_id
|
||||||
WHERE gm.group_id = s.group_id
|
WHERE gm.group_id = s.group_id
|
||||||
AND manager_player.telegram_id = @GmId
|
AND manager_player.platform = 'Telegram'
|
||||||
|
AND manager_player.external_user_id = @ExternalGmId
|
||||||
)
|
)
|
||||||
ORDER BY rp.created_at DESC
|
ORDER BY rp.created_at DESC
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
""",
|
""",
|
||||||
new { GmId = gmTelegramId, ChatId = chatId });
|
new { ExternalGmId = gmTelegramId.ToString(), ExternalChatId = chatId.ToString() });
|
||||||
|
|
||||||
if (proposal is null)
|
if (proposal is null)
|
||||||
return false;
|
return false;
|
||||||
@@ -92,8 +94,8 @@ public sealed class HandleRescheduleTimeInputHandler(
|
|||||||
"""
|
"""
|
||||||
SELECT p.id AS PlayerId,
|
SELECT p.id AS PlayerId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
p.telegram_id AS TelegramId
|
p.external_user_id::BIGINT AS TelegramId
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
WHERE sp.session_id = @SessionId
|
WHERE sp.session_id = @SessionId
|
||||||
@@ -363,7 +365,7 @@ public sealed class HandleRescheduleTimeInputHandler(
|
|||||||
"""
|
"""
|
||||||
SELECT sp.session_id AS SessionId,
|
SELECT sp.session_id AS SessionId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
sp.registration_status AS RegistrationStatus
|
sp.registration_status AS RegistrationStatus
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON sp.player_id = p.id
|
JOIN players p ON sp.player_id = p.id
|
||||||
|
|||||||
@@ -58,11 +58,12 @@ public sealed class HandleRescheduleVoteHandler(
|
|||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
WHERE sp.session_id = @SessionId
|
WHERE sp.session_id = @SessionId
|
||||||
AND p.telegram_id = @TelegramUserId
|
AND p.platform = 'Telegram'
|
||||||
|
AND p.external_user_id = @ExternalUserId
|
||||||
AND sp.is_gm = false
|
AND sp.is_gm = false
|
||||||
AND sp.registration_status = @Active
|
AND sp.registration_status = @Active
|
||||||
""",
|
""",
|
||||||
new { proposal.SessionId, command.TelegramUserId, Active = ParticipantRegistrationStatus.Active },
|
new { proposal.SessionId, ExternalUserId = command.TelegramUserId.ToString(), Active = ParticipantRegistrationStatus.Active },
|
||||||
transaction);
|
transaction);
|
||||||
|
|
||||||
if (playerId is null)
|
if (playerId is null)
|
||||||
@@ -91,8 +92,8 @@ public sealed class HandleRescheduleVoteHandler(
|
|||||||
"""
|
"""
|
||||||
SELECT p.id AS PlayerId,
|
SELECT p.id AS PlayerId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
p.telegram_id AS TelegramId
|
p.external_user_id::BIGINT AS TelegramId
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
WHERE sp.session_id = @SessionId
|
WHERE sp.session_id = @SessionId
|
||||||
@@ -120,7 +121,7 @@ public sealed class HandleRescheduleVoteHandler(
|
|||||||
SELECT rov.option_id AS OptionId,
|
SELECT rov.option_id AS OptionId,
|
||||||
p.id AS PlayerId,
|
p.id AS PlayerId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername
|
p.external_username AS TelegramUsername
|
||||||
FROM reschedule_option_votes rov
|
FROM reschedule_option_votes rov
|
||||||
JOIN players p ON p.id = rov.player_id
|
JOIN players p ON p.id = rov.player_id
|
||||||
WHERE rov.proposal_id = @ProposalId
|
WHERE rov.proposal_id = @ProposalId
|
||||||
|
|||||||
@@ -45,12 +45,13 @@ public sealed class InitiateRescheduleHandler(
|
|||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players p ON p.id = gm.player_id
|
JOIN players p ON p.id = gm.player_id
|
||||||
WHERE gm.group_id = s.group_id
|
WHERE gm.group_id = s.group_id
|
||||||
AND p.telegram_id = @TelegramUserId
|
AND p.platform = 'Telegram'
|
||||||
|
AND p.external_user_id = @ExternalUserId
|
||||||
) AS CanManage
|
) AS CanManage
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
WHERE s.id = @SessionId AND s.status != @Cancelled
|
WHERE s.id = @SessionId AND s.status != @Cancelled
|
||||||
""",
|
""",
|
||||||
new { command.SessionId, command.TelegramUserId, Cancelled = SessionStatus.Cancelled });
|
new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString(), Cancelled = SessionStatus.Cancelled });
|
||||||
|
|
||||||
if (session is null)
|
if (session is null)
|
||||||
{
|
{
|
||||||
@@ -83,10 +84,10 @@ public sealed class InitiateRescheduleHandler(
|
|||||||
// 3. Create proposal in AwaitingTime status
|
// 3. Create proposal in AwaitingTime status
|
||||||
await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
"""
|
"""
|
||||||
INSERT INTO reschedule_proposals (session_id, proposed_by, source_platform, status)
|
INSERT INTO reschedule_proposals (session_id, proposed_by_external_user_id, source_platform, status)
|
||||||
VALUES (@SessionId, @GmId, 'Telegram', 'AwaitingTime')
|
VALUES (@SessionId, @ProposedBy, 'Telegram', 'AwaitingTime')
|
||||||
""",
|
""",
|
||||||
new { command.SessionId, GmId = command.TelegramUserId });
|
new { command.SessionId, ProposedBy = command.TelegramUserId.ToString() });
|
||||||
|
|
||||||
logger.LogInformation("Reschedule initiated for session {SessionId} by GM {GmId}", command.SessionId, command.TelegramUserId);
|
logger.LogInformation("Reschedule initiated for session {SessionId} by GM {GmId}", command.SessionId, command.TelegramUserId);
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -79,7 +79,7 @@ public sealed class RescheduleVotingDeadlineService(
|
|||||||
"""
|
"""
|
||||||
SELECT rp.vote_message_id AS VoteMessageId,
|
SELECT rp.vote_message_id AS VoteMessageId,
|
||||||
s.batch_message_id AS BatchMessageId,
|
s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.thread_id AS ThreadId
|
s.thread_id AS ThreadId
|
||||||
FROM reschedule_proposals rp
|
FROM reschedule_proposals rp
|
||||||
JOIN sessions s ON s.id = rp.session_id
|
JOIN sessions s ON s.id = rp.session_id
|
||||||
@@ -169,7 +169,7 @@ public sealed class RescheduleVotingDeadlineService(
|
|||||||
"""
|
"""
|
||||||
SELECT sp.session_id AS SessionId,
|
SELECT sp.session_id AS SessionId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
sp.registration_status AS RegistrationStatus
|
sp.registration_status AS RegistrationStatus
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON sp.player_id = p.id
|
JOIN players p ON sp.player_id = p.id
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- V024: Deprecate legacy Telegram-specific columns
|
||||||
|
-- =============================================================
|
||||||
|
-- Scope: Complete platform migration by backfilling any remaining
|
||||||
|
-- external_* gaps and officially deprecating telegram_* columns.
|
||||||
|
-- No columns are dropped — rollback-safe.
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
-- 1. Backfill players platform identity (safeguard for any rows missed in V016)
|
||||||
|
UPDATE players
|
||||||
|
SET platform = 'Telegram',
|
||||||
|
external_user_id = telegram_id::TEXT,
|
||||||
|
external_username = telegram_username
|
||||||
|
WHERE platform IS NULL;
|
||||||
|
|
||||||
|
-- 2. Backfill game_groups platform identity (safeguard for any rows missed in V016)
|
||||||
|
UPDATE game_groups
|
||||||
|
SET platform = 'Telegram',
|
||||||
|
external_group_id = telegram_chat_id::TEXT
|
||||||
|
WHERE platform IS NULL;
|
||||||
|
|
||||||
|
-- 3. Add platform identity to calendar_subscriptions
|
||||||
|
ALTER TABLE calendar_subscriptions
|
||||||
|
ADD COLUMN user_platform VARCHAR(50),
|
||||||
|
ADD COLUMN user_external_id VARCHAR(255);
|
||||||
|
|
||||||
|
UPDATE calendar_subscriptions
|
||||||
|
SET user_external_id = user_telegram_id::TEXT,
|
||||||
|
user_platform = 'Telegram'
|
||||||
|
WHERE user_platform IS NULL;
|
||||||
|
|
||||||
|
-- 4. Migrate calendar subscription index
|
||||||
|
DROP INDEX IF EXISTS ix_calendar_subscriptions_user_telegram_id;
|
||||||
|
CREATE INDEX ix_calendar_subscriptions_user_external_id ON calendar_subscriptions (user_external_id);
|
||||||
|
|
||||||
|
-- 5. Deprecation comments on legacy columns
|
||||||
|
COMMENT ON COLUMN players.telegram_id IS 'DEPRECATED: use platform + external_user_id';
|
||||||
|
COMMENT ON COLUMN players.telegram_username IS 'DEPRECATED: use external_username';
|
||||||
|
COMMENT ON COLUMN game_groups.telegram_chat_id IS 'DEPRECATED: use platform + external_group_id';
|
||||||
|
COMMENT ON COLUMN game_groups.gm_telegram_id IS 'DEPRECATED: group ownership is tracked in group_managers';
|
||||||
|
COMMENT ON COLUMN calendar_subscriptions.user_telegram_id IS 'DEPRECATED: use user_platform + user_external_id';
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
-- =============================================================
|
||||||
|
-- V025: Backfill proposed_by_external_user_id for Telegram proposals
|
||||||
|
-- =============================================================
|
||||||
|
-- Scope: Ensure all reschedule_proposals have proposed_by_external_user_id
|
||||||
|
-- populated so that InitiateRescheduleHandler can stop writing proposed_by.
|
||||||
|
-- =============================================================
|
||||||
|
|
||||||
|
UPDATE reschedule_proposals
|
||||||
|
SET proposed_by_external_user_id = proposed_by::TEXT
|
||||||
|
WHERE proposed_by_external_user_id IS NULL
|
||||||
|
AND proposed_by IS NOT NULL;
|
||||||
@@ -74,7 +74,7 @@ public sealed class DiscordListSessionsHandler(
|
|||||||
var participants = await connection.QueryAsync<ParticipantBatchDto>(
|
var participants = await connection.QueryAsync<ParticipantBatchDto>(
|
||||||
@"SELECT sp.session_id as SessionId,
|
@"SELECT sp.session_id as SessionId,
|
||||||
p.display_name as DisplayName,
|
p.display_name as DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) as TelegramUsername,
|
p.external_username as TelegramUsername,
|
||||||
sp.registration_status as RegistrationStatus
|
sp.registration_status as RegistrationStatus
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ public sealed class DiscordRescheduleVotingDeadlineService(
|
|||||||
"""
|
"""
|
||||||
SELECT sp.session_id AS SessionId,
|
SELECT sp.session_id AS SessionId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
sp.registration_status AS RegistrationStatus
|
sp.registration_status AS RegistrationStatus
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
|
|||||||
@@ -56,8 +56,8 @@ public sealed class HandleRsvpHandler(
|
|||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
WHERE sp.session_id = @SessionId
|
WHERE sp.session_id = @SessionId
|
||||||
AND COALESCE(p.platform, 'Telegram') = @Platform
|
AND p.platform = @Platform
|
||||||
AND COALESCE(p.external_user_id, p.telegram_id::TEXT) = @ExternalUserId
|
AND p.external_user_id = @ExternalUserId
|
||||||
AND sp.is_gm = false
|
AND sp.is_gm = false
|
||||||
AND sp.registration_status = @Active
|
AND sp.registration_status = @Active
|
||||||
)
|
)
|
||||||
@@ -90,8 +90,8 @@ public sealed class HandleRsvpHandler(
|
|||||||
AND player_id = (
|
AND player_id = (
|
||||||
SELECT id
|
SELECT id
|
||||||
FROM players
|
FROM players
|
||||||
WHERE COALESCE(platform, 'Telegram') = @Platform
|
WHERE platform = @Platform
|
||||||
AND COALESCE(external_user_id, telegram_id::TEXT) = @ExternalUserId
|
AND external_user_id = @ExternalUserId
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)
|
)
|
||||||
AND registration_status = @Active
|
AND registration_status = @Active
|
||||||
@@ -265,10 +265,10 @@ public sealed class HandleRsvpHandler(
|
|||||||
|
|
||||||
var participants = (await connection.QueryAsync<ParticipantRsvpRow>(
|
var participants = (await connection.QueryAsync<ParticipantRsvpRow>(
|
||||||
"""
|
"""
|
||||||
SELECT COALESCE(p.platform, 'Telegram') AS Platform,
|
SELECT p.platform AS Platform,
|
||||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
p.external_user_id AS ExternalUserId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername,
|
p.external_username AS ExternalUsername,
|
||||||
sp.rsvp_status AS RsvpStatus,
|
sp.rsvp_status AS RsvpStatus,
|
||||||
sp.registration_status AS RegistrationStatus,
|
sp.registration_status AS RegistrationStatus,
|
||||||
sp.is_gm AS IsGm
|
sp.is_gm AS IsGm
|
||||||
@@ -312,23 +312,13 @@ public sealed class HandleRsvpHandler(
|
|||||||
var rows = await connection.QueryAsync<RsvpRecipientRow>(
|
var rows = await connection.QueryAsync<RsvpRecipientRow>(
|
||||||
"""
|
"""
|
||||||
SELECT DISTINCT
|
SELECT DISTINCT
|
||||||
COALESCE(p.platform, 'Telegram') AS Platform,
|
p.platform AS Platform,
|
||||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
p.external_user_id AS ExternalUserId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername
|
p.external_username AS ExternalUsername
|
||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
JOIN players p ON p.id = gm.player_id
|
JOIN players p ON p.id = gm.player_id
|
||||||
WHERE gm.group_id = @GroupId
|
WHERE gm.group_id = @GroupId
|
||||||
UNION
|
|
||||||
SELECT DISTINCT
|
|
||||||
COALESCE(p.platform, 'Telegram') AS Platform,
|
|
||||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
|
||||||
p.display_name AS DisplayName,
|
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername
|
|
||||||
FROM game_groups g
|
|
||||||
JOIN players p ON p.telegram_id = g.gm_telegram_id
|
|
||||||
WHERE g.id = @GroupId
|
|
||||||
AND g.gm_telegram_id IS NOT NULL
|
|
||||||
""",
|
""",
|
||||||
new { GroupId = groupId },
|
new { GroupId = groupId },
|
||||||
transaction);
|
transaction);
|
||||||
|
|||||||
+6
-6
@@ -45,10 +45,10 @@ public sealed class SendConfirmationHandler(
|
|||||||
s.title,
|
s.title,
|
||||||
s.scheduled_at AS ScheduledAt,
|
s.scheduled_at AS ScheduledAt,
|
||||||
s.group_id AS GroupId,
|
s.group_id AS GroupId,
|
||||||
COALESCE(g.platform, 'Telegram') AS Platform,
|
g.platform AS Platform,
|
||||||
COALESCE(g.external_group_id, g.telegram_chat_id::TEXT) AS ExternalGroupId,
|
g.external_group_id AS ExternalGroupId,
|
||||||
g.name AS DisplayName,
|
g.name AS DisplayName,
|
||||||
COALESCE(g.external_channel_id, g.telegram_chat_id::TEXT) AS ExternalChannelId,
|
g.external_channel_id AS ExternalChannelId,
|
||||||
s.thread_id AS ThreadId,
|
s.thread_id AS ThreadId,
|
||||||
s.notification_mode AS NotificationMode
|
s.notification_mode AS NotificationMode
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
@@ -65,10 +65,10 @@ public sealed class SendConfirmationHandler(
|
|||||||
|
|
||||||
var participants = (await connection.QueryAsync<ConfirmationParticipantRow>(
|
var participants = (await connection.QueryAsync<ConfirmationParticipantRow>(
|
||||||
"""
|
"""
|
||||||
SELECT COALESCE(p.platform, 'Telegram') AS Platform,
|
SELECT p.platform AS Platform,
|
||||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
p.external_user_id AS ExternalUserId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername,
|
p.external_username AS ExternalUsername,
|
||||||
sp.rsvp_status AS RsvpStatus,
|
sp.rsvp_status AS RsvpStatus,
|
||||||
sp.registration_status AS RegistrationStatus,
|
sp.registration_status AS RegistrationStatus,
|
||||||
sp.is_gm AS IsGm
|
sp.is_gm AS IsGm
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ public sealed class SendJoinLinkHandler(
|
|||||||
s.title,
|
s.title,
|
||||||
s.join_link AS JoinLink,
|
s.join_link AS JoinLink,
|
||||||
s.scheduled_at AS ScheduledAt,
|
s.scheduled_at AS ScheduledAt,
|
||||||
COALESCE(g.platform, 'Telegram') AS Platform,
|
g.platform AS Platform,
|
||||||
COALESCE(g.external_group_id, g.telegram_chat_id::TEXT) AS ExternalGroupId,
|
g.external_group_id AS ExternalGroupId,
|
||||||
g.name AS DisplayName,
|
g.name AS DisplayName,
|
||||||
COALESCE(g.external_channel_id, g.telegram_chat_id::TEXT) AS ExternalChannelId,
|
g.external_channel_id AS ExternalChannelId,
|
||||||
s.thread_id AS ThreadId,
|
s.thread_id AS ThreadId,
|
||||||
s.notification_mode AS NotificationMode
|
s.notification_mode AS NotificationMode
|
||||||
FROM sessions s
|
FROM sessions s
|
||||||
@@ -58,14 +58,14 @@ public sealed class SendJoinLinkHandler(
|
|||||||
WHERE s.id = @SessionId
|
WHERE s.id = @SessionId
|
||||||
AND s.status = @Confirmed
|
AND s.status = @Confirmed
|
||||||
AND (
|
AND (
|
||||||
(COALESCE(g.platform, 'Telegram') = 'Telegram' AND s.link_message_id IS NULL)
|
(g.platform = 'Telegram' AND s.link_message_id IS NULL)
|
||||||
OR (
|
OR (
|
||||||
COALESCE(g.platform, 'Telegram') <> 'Telegram'
|
g.platform <> 'Telegram'
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM platform_messages pm
|
FROM platform_messages pm
|
||||||
WHERE pm.session_id = s.id
|
WHERE pm.session_id = s.id
|
||||||
AND pm.platform = COALESCE(g.platform, 'Telegram')
|
AND pm.platform = g.platform
|
||||||
AND pm.purpose = 'join_link'
|
AND pm.purpose = 'join_link'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -81,10 +81,10 @@ public sealed class SendJoinLinkHandler(
|
|||||||
|
|
||||||
var players = (await connection.QueryAsync<JoinLinkPlayerRow>(
|
var players = (await connection.QueryAsync<JoinLinkPlayerRow>(
|
||||||
"""
|
"""
|
||||||
SELECT COALESCE(p.platform, 'Telegram') AS Platform,
|
SELECT p.platform AS Platform,
|
||||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
p.external_user_id AS ExternalUserId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername,
|
p.external_username AS ExternalUsername,
|
||||||
sp.rsvp_status AS RsvpStatus,
|
sp.rsvp_status AS RsvpStatus,
|
||||||
sp.registration_status AS RegistrationStatus,
|
sp.registration_status AS RegistrationStatus,
|
||||||
sp.is_gm AS IsGm
|
sp.is_gm AS IsGm
|
||||||
|
|||||||
+3
-3
@@ -56,10 +56,10 @@ public sealed class SendOneHourReminderHandler(
|
|||||||
|
|
||||||
var recipients = (await connection.QueryAsync<OneHourReminderRecipientRow>(
|
var recipients = (await connection.QueryAsync<OneHourReminderRecipientRow>(
|
||||||
"""
|
"""
|
||||||
SELECT COALESCE(p.platform, 'Telegram') AS Platform,
|
SELECT p.platform AS Platform,
|
||||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
p.external_user_id AS ExternalUserId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername
|
p.external_username AS ExternalUsername
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
WHERE sp.session_id = @SessionId
|
WHERE sp.session_id = @SessionId
|
||||||
|
|||||||
@@ -40,30 +40,19 @@ public sealed class JoinSessionHandler(
|
|||||||
{
|
{
|
||||||
// 1. Убеждаемся, что игрок есть в базе
|
// 1. Убеждаемся, что игрок есть в базе
|
||||||
var platform = command.User.Platform.ToString();
|
var platform = command.User.Platform.ToString();
|
||||||
var legacyTelegramId = command.User.Platform == PlatformKind.Telegram
|
|
||||||
? long.Parse(command.User.ExternalUserId, CultureInfo.InvariantCulture)
|
|
||||||
: (long?)null;
|
|
||||||
var legacyTelegramUsername = command.User.Platform == PlatformKind.Telegram
|
|
||||||
? command.User.ExternalUsername
|
|
||||||
: null;
|
|
||||||
|
|
||||||
var playerId = await connection.ExecuteScalarAsync<Guid>(
|
var playerId = await connection.ExecuteScalarAsync<Guid>(
|
||||||
@"INSERT INTO players (telegram_id, display_name, telegram_username, platform, external_user_id, external_username)
|
@"INSERT INTO players (display_name, platform, external_user_id, external_username)
|
||||||
VALUES (@LegacyTelegramId, @Name, @LegacyTelegramUsername, @Platform, @ExternalUserId, @ExternalUsername)
|
VALUES (@Name, @Platform, @ExternalUserId, @ExternalUsername)
|
||||||
ON CONFLICT (platform, external_user_id)
|
ON CONFLICT (platform, external_user_id)
|
||||||
WHERE platform IS NOT NULL AND external_user_id IS NOT NULL
|
WHERE platform IS NOT NULL AND external_user_id IS NOT NULL
|
||||||
DO UPDATE
|
DO UPDATE
|
||||||
SET display_name = EXCLUDED.display_name,
|
SET display_name = EXCLUDED.display_name,
|
||||||
telegram_username = COALESCE(EXCLUDED.telegram_username, players.telegram_username),
|
|
||||||
platform = EXCLUDED.platform,
|
|
||||||
external_user_id = EXCLUDED.external_user_id,
|
|
||||||
external_username = EXCLUDED.external_username
|
external_username = EXCLUDED.external_username
|
||||||
RETURNING id;",
|
RETURNING id;",
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
LegacyTelegramId = legacyTelegramId,
|
|
||||||
Name = command.User.DisplayName,
|
Name = command.User.DisplayName,
|
||||||
LegacyTelegramUsername = legacyTelegramUsername,
|
|
||||||
Platform = platform,
|
Platform = platform,
|
||||||
command.User.ExternalUserId,
|
command.User.ExternalUserId,
|
||||||
command.User.ExternalUsername
|
command.User.ExternalUsername
|
||||||
@@ -155,7 +144,7 @@ public sealed class JoinSessionHandler(
|
|||||||
var batchParticipants = await connection.QueryAsync<ParticipantBatchDto>(
|
var batchParticipants = await connection.QueryAsync<ParticipantBatchDto>(
|
||||||
@"SELECT sp.session_id as SessionId,
|
@"SELECT sp.session_id as SessionId,
|
||||||
p.display_name as DisplayName,
|
p.display_name as DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) as TelegramUsername,
|
p.external_username as TelegramUsername,
|
||||||
sp.registration_status as RegistrationStatus
|
sp.registration_status as RegistrationStatus
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON sp.player_id = p.id
|
JOIN players p ON sp.player_id = p.id
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ public sealed class LeaveSessionHandler(
|
|||||||
"""
|
"""
|
||||||
SELECT sp.session_id AS SessionId,
|
SELECT sp.session_id AS SessionId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
sp.registration_status AS RegistrationStatus
|
sp.registration_status AS RegistrationStatus
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON sp.player_id = p.id
|
JOIN players p ON sp.player_id = p.id
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ public sealed class RescheduleVotingFinalizer(
|
|||||||
"""
|
"""
|
||||||
SELECT p.id AS PlayerId,
|
SELECT p.id AS PlayerId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
p.telegram_id AS TelegramId
|
p.external_user_id::BIGINT AS TelegramId
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
WHERE sp.session_id = @SessionId
|
WHERE sp.session_id = @SessionId
|
||||||
@@ -107,7 +107,7 @@ public sealed class RescheduleVotingFinalizer(
|
|||||||
SELECT rov.option_id AS OptionId,
|
SELECT rov.option_id AS OptionId,
|
||||||
p.id AS PlayerId,
|
p.id AS PlayerId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername
|
p.external_username AS TelegramUsername
|
||||||
FROM reschedule_option_votes rov
|
FROM reschedule_option_votes rov
|
||||||
JOIN players p ON p.id = rov.player_id
|
JOIN players p ON p.id = rov.player_id
|
||||||
WHERE rov.proposal_id = @ProposalId
|
WHERE rov.proposal_id = @ProposalId
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div class="nav-version">v3.0.10</div>
|
<div class="nav-version">v3.1.0</div>
|
||||||
</div>
|
</div>
|
||||||
</Authorized>
|
</Authorized>
|
||||||
<NotAuthorized>
|
<NotAuthorized>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource)
|
|||||||
public string GenerateToken() => Guid.NewGuid().ToString("N");
|
public string GenerateToken() => Guid.NewGuid().ToString("N");
|
||||||
|
|
||||||
public async Task<string> CreateSubscriptionAsync(
|
public async Task<string> CreateSubscriptionAsync(
|
||||||
long userTelegramId,
|
string userPlatform,
|
||||||
|
string userExternalId,
|
||||||
Guid? groupId,
|
Guid? groupId,
|
||||||
CalendarSubscriptionFilter filter,
|
CalendarSubscriptionFilter filter,
|
||||||
CancellationToken ct = default)
|
CancellationToken ct = default)
|
||||||
@@ -20,9 +21,9 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource)
|
|||||||
var token = GenerateToken();
|
var token = GenerateToken();
|
||||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||||
await connection.ExecuteAsync(
|
await connection.ExecuteAsync(
|
||||||
@"INSERT INTO calendar_subscriptions (id, token, user_telegram_id, group_id, filter_type, created_at, expires_at)
|
@"INSERT INTO calendar_subscriptions (id, token, user_platform, user_external_id, group_id, filter_type, created_at, expires_at)
|
||||||
VALUES (gen_random_uuid(), @token, @userTelegramId, @groupId, @filterType, now(), NULL)",
|
VALUES (gen_random_uuid(), @token, @userPlatform, @userExternalId, @groupId, @filterType, now(), NULL)",
|
||||||
new { token, userTelegramId, groupId, filterType = (int)filter });
|
new { token, userPlatform, userExternalId, groupId, filterType = (int)filter });
|
||||||
return token;
|
return token;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +32,7 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource)
|
|||||||
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
await using var connection = await dataSource.OpenConnectionAsync(ct);
|
||||||
|
|
||||||
var subscription = await connection.QueryFirstOrDefaultAsync<SubscriptionRecord>(
|
var subscription = await connection.QueryFirstOrDefaultAsync<SubscriptionRecord>(
|
||||||
@"SELECT id, user_telegram_id as UserTelegramId, group_id as GroupId, filter_type as FilterType
|
@"SELECT id, group_id as GroupId, filter_type as FilterType
|
||||||
FROM calendar_subscriptions
|
FROM calendar_subscriptions
|
||||||
WHERE token = @token
|
WHERE token = @token
|
||||||
AND (expires_at IS NULL OR expires_at > now())",
|
AND (expires_at IS NULL OR expires_at > now())",
|
||||||
@@ -88,6 +89,6 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource)
|
|||||||
.Replace("\n", "\\n")
|
.Replace("\n", "\\n")
|
||||||
.Replace("\r", "");
|
.Replace("\r", "");
|
||||||
|
|
||||||
private sealed record SubscriptionRecord(Guid Id, long UserTelegramId, Guid? GroupId, int FilterType);
|
private sealed record SubscriptionRecord(Guid Id, Guid? GroupId, int FilterType);
|
||||||
private sealed record CalendarSessionDto(Guid Id, string Title, DateTime ScheduledAt);
|
private sealed record CalendarSessionDto(Guid Id, string Title, DateTime ScheduledAt);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ public sealed class SessionService(
|
|||||||
GROUP BY gm.group_id
|
GROUP BY gm.group_id
|
||||||
)
|
)
|
||||||
SELECT g.id,
|
SELECT g.id,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
g.external_group_id AS ExternalGroupId,
|
g.external_group_id AS ExternalGroupId,
|
||||||
COALESCE(NULLIF(g.name, g.external_group_id), latest_session.title, g.name) AS Name,
|
COALESCE(NULLIF(g.name, g.external_group_id), latest_session.title, g.name) AS Name,
|
||||||
g.platform AS Platform,
|
g.platform AS Platform,
|
||||||
@@ -151,7 +151,7 @@ public sealed class SessionService(
|
|||||||
return await conn.QuerySingleOrDefaultAsync<WebGameGroup>(
|
return await conn.QuerySingleOrDefaultAsync<WebGameGroup>(
|
||||||
"""
|
"""
|
||||||
SELECT g.id,
|
SELECT g.id,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
g.external_group_id AS ExternalGroupId,
|
g.external_group_id AS ExternalGroupId,
|
||||||
COALESCE(NULLIF(g.name, g.external_group_id), latest_session.title, g.name) AS Name,
|
COALESCE(NULLIF(g.name, g.external_group_id), latest_session.title, g.name) AS Name,
|
||||||
g.platform AS Platform,
|
g.platform AS Platform,
|
||||||
@@ -213,11 +213,11 @@ public sealed class SessionService(
|
|||||||
await using var conn = await dataSource.OpenConnectionAsync();
|
await using var conn = await dataSource.OpenConnectionAsync();
|
||||||
return (await conn.QueryAsync<WebGroupManager>(
|
return (await conn.QueryAsync<WebGroupManager>(
|
||||||
"""
|
"""
|
||||||
SELECT COALESCE(p.telegram_id, 0) AS TelegramId,
|
SELECT COALESCE(p.external_user_id::BIGINT, 0) AS TelegramId,
|
||||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
p.external_user_id AS ExternalUserId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername,
|
p.external_username AS ExternalUsername,
|
||||||
gm.role AS Role,
|
gm.role AS Role,
|
||||||
gm.created_at AS AddedAt
|
gm.created_at AS AddedAt
|
||||||
FROM group_managers gm
|
FROM group_managers gm
|
||||||
@@ -238,7 +238,7 @@ public sealed class SessionService(
|
|||||||
SELECT
|
SELECT
|
||||||
p.id AS PlayerId,
|
p.id AS PlayerId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername,
|
p.external_username AS ExternalUsername,
|
||||||
COUNT(DISTINCT s.id) AS TotalSessions,
|
COUNT(DISTINCT s.id) AS TotalSessions,
|
||||||
COUNT(DISTINCT CASE WHEN sp.rsvp_status = 'Confirmed' THEN s.id END) AS ConfirmedCount,
|
COUNT(DISTINCT CASE WHEN sp.rsvp_status = 'Confirmed' THEN s.id END) AS ConfirmedCount,
|
||||||
COUNT(DISTINCT CASE WHEN sp.rsvp_status = 'Declined' THEN s.id END) AS DeclinedCount,
|
COUNT(DISTINCT CASE WHEN sp.rsvp_status = 'Declined' THEN s.id END) AS DeclinedCount,
|
||||||
@@ -257,7 +257,7 @@ public sealed class SessionService(
|
|||||||
WHERE s.group_id = @GroupId
|
WHERE s.group_id = @GroupId
|
||||||
AND s.scheduled_at <= now()
|
AND s.scheduled_at <= now()
|
||||||
AND sp.is_gm = false
|
AND sp.is_gm = false
|
||||||
GROUP BY p.id, p.display_name, p.external_username, p.telegram_username
|
GROUP BY p.id, p.display_name, p.external_username
|
||||||
ORDER BY AttendanceRate DESC, ConfirmedCount DESC
|
ORDER BY AttendanceRate DESC, ConfirmedCount DESC
|
||||||
""",
|
""",
|
||||||
new { GroupId = groupId })).ToList();
|
new { GroupId = groupId })).ToList();
|
||||||
@@ -356,7 +356,7 @@ public sealed class SessionService(
|
|||||||
return (await conn.QueryAsync<WebSession>(
|
return (await conn.QueryAsync<WebSession>(
|
||||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
||||||
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
||||||
@@ -394,7 +394,7 @@ public sealed class SessionService(
|
|||||||
return await conn.QuerySingleOrDefaultAsync<WebSession>(
|
return await conn.QuerySingleOrDefaultAsync<WebSession>(
|
||||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
COALESCE(active_counts.count, 0)::int AS ActivePlayerCount,
|
||||||
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount,
|
||||||
@@ -453,7 +453,7 @@ public sealed class SessionService(
|
|||||||
var oldSession = await conn.QuerySingleOrDefaultAsync<WebSession>(
|
var oldSession = await conn.QuerySingleOrDefaultAsync<WebSession>(
|
||||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
0 AS ActivePlayerCount,
|
0 AS ActivePlayerCount,
|
||||||
0 AS WaitlistedPlayerCount,
|
0 AS WaitlistedPlayerCount,
|
||||||
@@ -539,7 +539,7 @@ public sealed class SessionService(
|
|||||||
var session = await conn.QuerySingleOrDefaultAsync<WebSession>(
|
var session = await conn.QuerySingleOrDefaultAsync<WebSession>(
|
||||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
0 AS ActivePlayerCount,
|
0 AS ActivePlayerCount,
|
||||||
0 AS WaitlistedPlayerCount,
|
0 AS WaitlistedPlayerCount,
|
||||||
@@ -638,11 +638,11 @@ public sealed class SessionService(
|
|||||||
return (await conn.QueryAsync<WebParticipant>(
|
return (await conn.QueryAsync<WebParticipant>(
|
||||||
"""
|
"""
|
||||||
SELECT sp.id AS Id,
|
SELECT sp.id AS Id,
|
||||||
COALESCE(p.telegram_id, 0) AS TelegramId,
|
COALESCE(p.external_user_id::BIGINT, 0) AS TelegramId,
|
||||||
COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId,
|
p.external_user_id AS ExternalUserId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
COALESCE(p.external_username, p.telegram_username) AS ExternalUsername,
|
p.external_username AS ExternalUsername,
|
||||||
sp.rsvp_status AS RsvpStatus,
|
sp.rsvp_status AS RsvpStatus,
|
||||||
sp.registration_status AS RegistrationStatus,
|
sp.registration_status AS RegistrationStatus,
|
||||||
sp.is_gm AS IsGm,
|
sp.is_gm AS IsGm,
|
||||||
@@ -665,7 +665,7 @@ public sealed class SessionService(
|
|||||||
var session = await conn.QuerySingleOrDefaultAsync<WebSession>(
|
var session = await conn.QuerySingleOrDefaultAsync<WebSession>(
|
||||||
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
@"SELECT s.id, s.group_id AS GroupId, s.title, s.scheduled_at AS ScheduledAt, s.status, s.join_link AS JoinLink,
|
||||||
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
s.batch_id AS BatchId, s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
0 AS ActivePlayerCount,
|
0 AS ActivePlayerCount,
|
||||||
0 AS WaitlistedPlayerCount,
|
0 AS WaitlistedPlayerCount,
|
||||||
@@ -686,9 +686,9 @@ public sealed class SessionService(
|
|||||||
var participant = await conn.QuerySingleOrDefaultAsync<WebParticipant>(
|
var participant = await conn.QuerySingleOrDefaultAsync<WebParticipant>(
|
||||||
"""
|
"""
|
||||||
SELECT sp.id AS Id,
|
SELECT sp.id AS Id,
|
||||||
p.telegram_id AS TelegramId,
|
p.external_user_id::BIGINT AS TelegramId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
sp.rsvp_status AS RsvpStatus,
|
sp.rsvp_status AS RsvpStatus,
|
||||||
sp.registration_status AS RegistrationStatus,
|
sp.registration_status AS RegistrationStatus,
|
||||||
sp.is_gm AS IsGm,
|
sp.is_gm AS IsGm,
|
||||||
@@ -871,7 +871,7 @@ public sealed class SessionService(
|
|||||||
s.status AS Status,
|
s.status AS Status,
|
||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
s.batch_message_id AS BatchMessageId,
|
s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.thread_id AS ThreadId,
|
s.thread_id AS ThreadId,
|
||||||
s.topic_created_by_bot AS TopicCreatedByBot,
|
s.topic_created_by_bot AS TopicCreatedByBot,
|
||||||
s.notification_mode AS NotificationMode
|
s.notification_mode AS NotificationMode
|
||||||
@@ -955,7 +955,7 @@ public sealed class SessionService(
|
|||||||
s.status AS Status,
|
s.status AS Status,
|
||||||
s.max_players AS MaxPlayers,
|
s.max_players AS MaxPlayers,
|
||||||
s.batch_message_id AS BatchMessageId,
|
s.batch_message_id AS BatchMessageId,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
s.thread_id AS ThreadId,
|
s.thread_id AS ThreadId,
|
||||||
s.topic_created_by_bot AS TopicCreatedByBot,
|
s.topic_created_by_bot AS TopicCreatedByBot,
|
||||||
s.notification_mode AS NotificationMode
|
s.notification_mode AS NotificationMode
|
||||||
@@ -1177,7 +1177,7 @@ public sealed class SessionService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var group = await conn.QuerySingleOrDefaultAsync<WebTemplateGroupDto>(
|
var group = await conn.QuerySingleOrDefaultAsync<WebTemplateGroupDto>(
|
||||||
"SELECT telegram_chat_id AS TelegramChatId FROM game_groups WHERE id = @GroupId",
|
"SELECT external_group_id::BIGINT AS TelegramChatId FROM game_groups WHERE id = @GroupId",
|
||||||
new { GroupId = groupId },
|
new { GroupId = groupId },
|
||||||
transaction);
|
transaction);
|
||||||
|
|
||||||
@@ -1248,7 +1248,7 @@ public sealed class SessionService(
|
|||||||
{
|
{
|
||||||
return (await conn.QueryAsync<WebDirectNotificationRecipient>(
|
return (await conn.QueryAsync<WebDirectNotificationRecipient>(
|
||||||
"""
|
"""
|
||||||
SELECT p.telegram_id AS TelegramId,
|
SELECT p.external_user_id::BIGINT AS TelegramId,
|
||||||
p.display_name AS DisplayName
|
p.display_name AS DisplayName
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
@@ -1265,7 +1265,7 @@ public sealed class SessionService(
|
|||||||
{
|
{
|
||||||
return (await conn.QueryAsync<WebDirectNotificationRecipient>(
|
return (await conn.QueryAsync<WebDirectNotificationRecipient>(
|
||||||
"""
|
"""
|
||||||
SELECT DISTINCT p.telegram_id AS TelegramId,
|
SELECT DISTINCT p.external_user_id::BIGINT AS TelegramId,
|
||||||
p.display_name AS DisplayName
|
p.display_name AS DisplayName
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON p.id = sp.player_id
|
JOIN players p ON p.id = sp.player_id
|
||||||
@@ -1318,7 +1318,7 @@ public sealed class SessionService(
|
|||||||
var participants = (await conn.QueryAsync<ParticipantBatchDto>(
|
var participants = (await conn.QueryAsync<ParticipantBatchDto>(
|
||||||
@"SELECT sp.session_id AS SessionId,
|
@"SELECT sp.session_id AS SessionId,
|
||||||
p.display_name AS DisplayName,
|
p.display_name AS DisplayName,
|
||||||
p.telegram_username AS TelegramUsername,
|
p.external_username AS TelegramUsername,
|
||||||
sp.registration_status AS RegistrationStatus
|
sp.registration_status AS RegistrationStatus
|
||||||
FROM session_participants sp
|
FROM session_participants sp
|
||||||
JOIN players p ON sp.player_id = p.id
|
JOIN players p ON sp.player_id = p.id
|
||||||
@@ -1355,7 +1355,7 @@ public sealed class SessionService(
|
|||||||
s.group_id AS GroupId,
|
s.group_id AS GroupId,
|
||||||
(array_agg(s.title ORDER BY s.scheduled_at))[1] AS Title,
|
(array_agg(s.title ORDER BY s.scheduled_at))[1] AS Title,
|
||||||
(array_agg(s.join_link ORDER BY s.scheduled_at))[1] AS JoinLink,
|
(array_agg(s.join_link ORDER BY s.scheduled_at))[1] AS JoinLink,
|
||||||
g.telegram_chat_id AS TelegramChatId,
|
g.external_group_id::BIGINT AS TelegramChatId,
|
||||||
(array_agg(s.batch_message_id ORDER BY s.scheduled_at))[1] AS BatchMessageId,
|
(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
|
(array_agg(s.notification_mode ORDER BY s.scheduled_at))[1] AS NotificationMode
|
||||||
@@ -1363,7 +1363,7 @@ public sealed class SessionService(
|
|||||||
JOIN game_groups g ON g.id = s.group_id
|
JOIN game_groups g ON g.id = s.group_id
|
||||||
WHERE s.batch_id = @BatchId
|
WHERE s.batch_id = @BatchId
|
||||||
AND s.group_id = @GroupId
|
AND s.group_id = @GroupId
|
||||||
GROUP BY s.batch_id, s.group_id, g.telegram_chat_id
|
GROUP BY s.batch_id, s.group_id, g.external_group_id
|
||||||
""",
|
""",
|
||||||
new { BatchId = batchId, GroupId = groupId },
|
new { BatchId = batchId, GroupId = groupId },
|
||||||
transaction);
|
transaction);
|
||||||
|
|||||||
@@ -117,6 +117,28 @@ public sealed class PlatformIdentityMigrationTests
|
|||||||
Assert.Contains("telegram_id DROP NOT NULL", migration, StringComparison.Ordinal);
|
Assert.Contains("telegram_id DROP NOT NULL", migration, StringComparison.Ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MigrationV024_ShouldDeprecateTelegramColumns()
|
||||||
|
{
|
||||||
|
var migration = await ReadRepositoryFileAsync("src/GmRelay.Bot/Migrations/V024__deprecate_telegram_columns.sql");
|
||||||
|
|
||||||
|
Assert.Contains("UPDATE players", migration, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("UPDATE game_groups", migration, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("DEPRECATED", migration, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("calendar_subscriptions", migration, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("user_platform", migration, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("user_external_id", migration, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MigrationV025_ShouldBackfillRescheduleProposals()
|
||||||
|
{
|
||||||
|
var migration = await ReadRepositoryFileAsync("src/GmRelay.Bot/Migrations/V025__reschedule_proposals_telegram_external.sql");
|
||||||
|
|
||||||
|
Assert.Contains("reschedule_proposals", migration, StringComparison.Ordinal);
|
||||||
|
Assert.Contains("proposed_by_external_user_id", migration, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<string> ReadRepositoryFileAsync(string relativePath)
|
private static async Task<string> ReadRepositoryFileAsync(string relativePath)
|
||||||
{
|
{
|
||||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
|
|||||||
Reference in New Issue
Block a user