diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 8b89df8..400f1ed 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -6,7 +6,7 @@ on: - main env: - VERSION: 3.0.10 + VERSION: 3.1.0 jobs: # ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами) diff --git a/Directory.Build.props b/Directory.Build.props index d97e1c6..b947ac1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 3.0.10 + 3.1.0 net10.0 preview enable diff --git a/compose.yaml b/compose.yaml index ecf47a4..a69ee9e 100644 --- a/compose.yaml +++ b/compose.yaml @@ -49,7 +49,7 @@ services: crond -f bot: - image: git.codeanddice.ru/toutsu/gmrelay-bot:3.0.10 + image: git.codeanddice.ru/toutsu/gmrelay-bot:3.1.0 restart: always depends_on: db: @@ -67,7 +67,7 @@ services: retries: 3 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 depends_on: db: @@ -84,7 +84,7 @@ services: retries: 3 web: - image: git.codeanddice.ru/toutsu/gmrelay-web:3.0.10 + image: git.codeanddice.ru/toutsu/gmrelay-web:3.1.0 restart: always depends_on: db: diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs index d963c59..56fffce 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs @@ -42,12 +42,13 @@ public sealed class CancelSessionHandler( 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 + AND p.platform = 'Telegram' + AND p.external_user_id = @ExternalUserId ) AS CanManage FROM sessions s WHERE s.id = @SessionId """, - new { command.SessionId, command.TelegramUserId }, transaction); + new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString() }, transaction); if (session == null) { @@ -89,7 +90,7 @@ public sealed class CancelSessionHandler( var directRecipients = (await connection.QueryAsync( """ - SELECT p.telegram_id AS TelegramId, + SELECT p.external_user_id::BIGINT AS TelegramId, p.display_name AS DisplayName FROM session_participants sp JOIN players p ON sp.player_id = p.id diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs index 9beea10..95e3440 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs @@ -77,16 +77,15 @@ public sealed class CreateSessionHandler( { await connection.ExecuteAsync( """ - INSERT INTO players (telegram_id, display_name, telegram_username, platform, external_user_id, external_username) - VALUES (@TgId, @Name, @Username, 'Telegram', @TgId::TEXT, @Username) - ON CONFLICT (telegram_id) DO UPDATE + INSERT INTO players (display_name, platform, external_user_id, external_username) + VALUES (@Name, 'Telegram', @ExternalId, @Username) + 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, - telegram_username = EXCLUDED.telegram_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); + external_username = EXCLUDED.external_username; """, - new { TgId = gmId, Name = gmName, Username = gmUsername }, + new { ExternalId = gmId.ToString(), Name = gmName, Username = gmUsername }, transaction); var existingGroup = await connection.QuerySingleOrDefaultAsync( @@ -97,12 +96,14 @@ public sealed class CreateSessionHandler( FROM group_managers gm JOIN players p ON p.id = gm.player_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 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); Guid groupId; @@ -110,11 +111,11 @@ public sealed class CreateSessionHandler( { groupId = await connection.ExecuteScalarAsync( """ - INSERT INTO game_groups (telegram_chat_id, name, gm_telegram_id, platform, external_group_id) - VALUES (@ChatId, @ChatName, @GmId, 'Telegram', @ChatId::TEXT) + INSERT INTO game_groups (name, platform, external_group_id) + VALUES (@ChatName, 'Telegram', @ExternalChatId) RETURNING id; """, - new { ChatId = chatId, ChatName = chatTitle, GmId = gmId }, + new { ExternalChatId = chatId.ToString(), ChatName = chatTitle }, transaction); await connection.ExecuteAsync( @@ -122,10 +123,11 @@ public sealed class CreateSessionHandler( INSERT INTO group_managers (group_id, player_id, role) SELECT @GroupId, p.id, @OwnerRole 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 """, - new { GroupId = groupId, GmId = gmId, OwnerRole = GroupManagerRoleExtensions.OwnerValue }, + new { GroupId = groupId, ExternalGmId = gmId.ToString(), OwnerRole = GroupManagerRoleExtensions.OwnerValue }, transaction); } else diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs index 2e6dd6c..0152913 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs @@ -41,13 +41,14 @@ public sealed class PromoteWaitlistedPlayerHandler( 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 + AND p.platform = 'Telegram' + AND p.external_user_id = @ExternalUserId ) AS CanManage FROM sessions s WHERE s.id = @SessionId FOR UPDATE """, - new { command.SessionId, command.TelegramUserId }, + new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString() }, transaction); if (session is null) @@ -150,7 +151,7 @@ public sealed class PromoteWaitlistedPlayerHandler( """ SELECT sp.session_id AS SessionId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, + p.external_username AS TelegramUsername, sp.registration_status AS RegistrationStatus FROM session_participants sp JOIN players p ON sp.player_id = p.id diff --git a/src/GmRelay.Bot/Features/Sessions/ExportCalendar/ExportCalendarHandler.cs b/src/GmRelay.Bot/Features/Sessions/ExportCalendar/ExportCalendarHandler.cs index 2bdea66..20cbd2b 100644 --- a/src/GmRelay.Bot/Features/Sessions/ExportCalendar/ExportCalendarHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/ExportCalendar/ExportCalendarHandler.cs @@ -24,11 +24,12 @@ public sealed class ExportCalendarHandler( @"SELECT s.id as Id, s.title as Title, s.scheduled_at as ScheduledAt" + " FROM sessions s" + " 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.scheduled_at > NOW()" + " 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(); @@ -75,13 +76,13 @@ public sealed class ExportCalendarHandler( { var token = Guid.NewGuid().ToString("N"); var groupId = await connection.QueryFirstOrDefaultAsync( - @"SELECT id FROM game_groups WHERE telegram_chat_id = @ChatId", - new { ChatId = message.Chat.Id }); + @"SELECT id FROM game_groups WHERE platform = 'Telegram' AND external_group_id = @ExternalChatId", + new { ExternalChatId = message.Chat.Id.ToString() }); await connection.ExecuteAsync( - @"INSERT INTO calendar_subscriptions (id, token, user_telegram_id, group_id, filter_type, created_at, expires_at) - VALUES (gen_random_uuid(), @token, @userTelegramId, @groupId, @filterType, now(), NULL)", - new { token, userTelegramId = senderId.Value, groupId, filterType = (int)CalendarSubscriptionFilter.SpecificGroup }); + @"INSERT INTO calendar_subscriptions (id, token, user_platform, user_external_id, group_id, filter_type, created_at, expires_at) + VALUES (gen_random_uuid(), @token, 'Telegram', @userExternalId, @groupId, @filterType, now(), NULL)", + new { token, userExternalId = senderId.Value.ToString(), groupId, filterType = (int)CalendarSubscriptionFilter.SpecificGroup }); subscriptionUrl = $"{baseUrl.TrimEnd('/')}/calendar/{token}.ics"; } diff --git a/src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs index 46ed206..0b150cf 100644 --- a/src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/ListSessions/DeleteSessionHandler.cs @@ -44,12 +44,13 @@ public sealed class DeleteSessionHandler( 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 + AND p.platform = 'Telegram' + AND p.external_user_id = @ExternalUserId ) AS CanManage FROM sessions s WHERE s.id = @SessionId """, - new { command.SessionId, command.TelegramUserId }, transaction); + new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString() }, transaction); if (session == null) { @@ -109,18 +110,22 @@ public sealed class DeleteSessionHandler( FROM group_managers gm JOIN players manager_player ON manager_player.id = gm.player_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 FROM sessions s JOIN game_groups g ON s.group_id = g.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 ORDER BY s.scheduled_at ASC", new { - ChatId = command.ChatId, - command.TelegramUserId, + ExternalChatId = command.ChatId.ToString(), + ExternalUserId = command.TelegramUserId.ToString(), Cancelled = SessionStatus.Cancelled, Active = ParticipantRegistrationStatus.Active, Waitlisted = ParticipantRegistrationStatus.Waitlisted diff --git a/src/GmRelay.Bot/Features/Sessions/ListSessions/ListSessionsHandler.cs b/src/GmRelay.Bot/Features/Sessions/ListSessions/ListSessionsHandler.cs index 8fc4175..662968d 100644 --- a/src/GmRelay.Bot/Features/Sessions/ListSessions/ListSessionsHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/ListSessions/ListSessionsHandler.cs @@ -74,18 +74,22 @@ public sealed class ListSessionsHandler( FROM group_managers gm JOIN players manager_player ON manager_player.id = gm.player_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 FROM sessions s JOIN game_groups g ON s.group_id = g.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 ORDER BY s.scheduled_at ASC", new { - ChatId = message.Chat.Id, - TelegramUserId = message.From?.Id, + ExternalChatId = message.Chat.Id.ToString(), + ExternalUserId = message.From?.Id.ToString(), Cancelled = SessionStatus.Cancelled, Active = ParticipantRegistrationStatus.Active, Waitlisted = ParticipantRegistrationStatus.Waitlisted diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs index c9bdb3c..f36acd5 100644 --- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs @@ -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, 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.notification_mode AS NotificationMode FROM reschedule_proposals rp JOIN sessions s ON s.id = rp.session_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 g.telegram_chat_id = @ChatId + AND g.platform = 'Telegram' + AND g.external_group_id = @ExternalChatId AND EXISTS ( SELECT 1 FROM group_managers gm JOIN players manager_player ON manager_player.id = gm.player_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 LIMIT 1 """, - new { GmId = gmTelegramId, ChatId = chatId }); + new { ExternalGmId = gmTelegramId.ToString(), ExternalChatId = chatId.ToString() }); if (proposal is null) return false; @@ -92,8 +94,8 @@ public sealed class HandleRescheduleTimeInputHandler( """ SELECT p.id AS PlayerId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, - p.telegram_id AS TelegramId + p.external_username AS TelegramUsername, + p.external_user_id::BIGINT AS TelegramId FROM session_participants sp JOIN players p ON p.id = sp.player_id WHERE sp.session_id = @SessionId @@ -363,7 +365,7 @@ public sealed class HandleRescheduleTimeInputHandler( """ SELECT sp.session_id AS SessionId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, + p.external_username AS TelegramUsername, sp.registration_status AS RegistrationStatus FROM session_participants sp JOIN players p ON sp.player_id = p.id diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleVoteHandler.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleVoteHandler.cs index 1e5d356..f7c0595 100644 --- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleVoteHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleVoteHandler.cs @@ -58,11 +58,12 @@ public sealed class HandleRescheduleVoteHandler( FROM session_participants sp JOIN players p ON p.id = sp.player_id 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.registration_status = @Active """, - new { proposal.SessionId, command.TelegramUserId, Active = ParticipantRegistrationStatus.Active }, + new { proposal.SessionId, ExternalUserId = command.TelegramUserId.ToString(), Active = ParticipantRegistrationStatus.Active }, transaction); if (playerId is null) @@ -91,8 +92,8 @@ public sealed class HandleRescheduleVoteHandler( """ SELECT p.id AS PlayerId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, - p.telegram_id AS TelegramId + p.external_username AS TelegramUsername, + p.external_user_id::BIGINT AS TelegramId FROM session_participants sp JOIN players p ON p.id = sp.player_id WHERE sp.session_id = @SessionId @@ -120,7 +121,7 @@ public sealed class HandleRescheduleVoteHandler( SELECT rov.option_id AS OptionId, p.id AS PlayerId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername + p.external_username AS TelegramUsername FROM reschedule_option_votes rov JOIN players p ON p.id = rov.player_id WHERE rov.proposal_id = @ProposalId diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs index 625fb29..0782253 100644 --- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs @@ -45,12 +45,13 @@ public sealed class InitiateRescheduleHandler( 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 + 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, command.TelegramUserId, Cancelled = SessionStatus.Cancelled }); + new { command.SessionId, ExternalUserId = command.TelegramUserId.ToString(), Cancelled = SessionStatus.Cancelled }); if (session is null) { @@ -83,10 +84,10 @@ public sealed class InitiateRescheduleHandler( // 3. Create proposal in AwaitingTime status await connection.ExecuteAsync( """ - INSERT INTO reschedule_proposals (session_id, proposed_by, source_platform, status) - VALUES (@SessionId, @GmId, 'Telegram', 'AwaitingTime') + INSERT INTO reschedule_proposals (session_id, proposed_by_external_user_id, source_platform, status) + 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); diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs index 6ffa482..78b48bf 100644 --- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs +++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs @@ -79,7 +79,7 @@ public sealed class RescheduleVotingDeadlineService( """ SELECT rp.vote_message_id AS VoteMessageId, s.batch_message_id AS BatchMessageId, - g.telegram_chat_id AS TelegramChatId, + g.external_group_id::BIGINT AS TelegramChatId, s.thread_id AS ThreadId FROM reschedule_proposals rp JOIN sessions s ON s.id = rp.session_id @@ -169,7 +169,7 @@ public sealed class RescheduleVotingDeadlineService( """ SELECT sp.session_id AS SessionId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, + p.external_username AS TelegramUsername, sp.registration_status AS RegistrationStatus FROM session_participants sp JOIN players p ON sp.player_id = p.id diff --git a/src/GmRelay.Bot/Migrations/V024__deprecate_telegram_columns.sql b/src/GmRelay.Bot/Migrations/V024__deprecate_telegram_columns.sql new file mode 100644 index 0000000..39cb260 --- /dev/null +++ b/src/GmRelay.Bot/Migrations/V024__deprecate_telegram_columns.sql @@ -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'; diff --git a/src/GmRelay.Bot/Migrations/V025__reschedule_proposals_telegram_external.sql b/src/GmRelay.Bot/Migrations/V025__reschedule_proposals_telegram_external.sql new file mode 100644 index 0000000..a363538 --- /dev/null +++ b/src/GmRelay.Bot/Migrations/V025__reschedule_proposals_telegram_external.sql @@ -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; diff --git a/src/GmRelay.DiscordBot/Features/Sessions/DiscordListSessionsHandler.cs b/src/GmRelay.DiscordBot/Features/Sessions/DiscordListSessionsHandler.cs index 276b8a3..e0917c0 100644 --- a/src/GmRelay.DiscordBot/Features/Sessions/DiscordListSessionsHandler.cs +++ b/src/GmRelay.DiscordBot/Features/Sessions/DiscordListSessionsHandler.cs @@ -74,7 +74,7 @@ public sealed class DiscordListSessionsHandler( var participants = await connection.QueryAsync( @"SELECT sp.session_id as SessionId, p.display_name as DisplayName, - COALESCE(p.external_username, p.telegram_username) as TelegramUsername, + p.external_username as TelegramUsername, sp.registration_status as RegistrationStatus FROM session_participants sp JOIN players p ON p.id = sp.player_id diff --git a/src/GmRelay.DiscordBot/Features/Sessions/DiscordRescheduleVotingDeadlineService.cs b/src/GmRelay.DiscordBot/Features/Sessions/DiscordRescheduleVotingDeadlineService.cs index 8acd428..a92dd7e 100644 --- a/src/GmRelay.DiscordBot/Features/Sessions/DiscordRescheduleVotingDeadlineService.cs +++ b/src/GmRelay.DiscordBot/Features/Sessions/DiscordRescheduleVotingDeadlineService.cs @@ -152,7 +152,7 @@ public sealed class DiscordRescheduleVotingDeadlineService( """ SELECT sp.session_id AS SessionId, p.display_name AS DisplayName, - COALESCE(p.external_username, p.telegram_username) AS TelegramUsername, + p.external_username AS TelegramUsername, sp.registration_status AS RegistrationStatus FROM session_participants sp JOIN players p ON p.id = sp.player_id diff --git a/src/GmRelay.Shared/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs b/src/GmRelay.Shared/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs index 6ecb9f6..2d9dce1 100644 --- a/src/GmRelay.Shared/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs +++ b/src/GmRelay.Shared/Features/Confirmation/HandleRsvp/HandleRsvpHandler.cs @@ -56,8 +56,8 @@ public sealed class HandleRsvpHandler( FROM session_participants sp JOIN players p ON p.id = sp.player_id WHERE sp.session_id = @SessionId - AND COALESCE(p.platform, 'Telegram') = @Platform - AND COALESCE(p.external_user_id, p.telegram_id::TEXT) = @ExternalUserId + AND p.platform = @Platform + AND p.external_user_id = @ExternalUserId AND sp.is_gm = false AND sp.registration_status = @Active ) @@ -90,8 +90,8 @@ public sealed class HandleRsvpHandler( AND player_id = ( SELECT id FROM players - WHERE COALESCE(platform, 'Telegram') = @Platform - AND COALESCE(external_user_id, telegram_id::TEXT) = @ExternalUserId + WHERE platform = @Platform + AND external_user_id = @ExternalUserId LIMIT 1 ) AND registration_status = @Active @@ -265,10 +265,10 @@ public sealed class HandleRsvpHandler( var participants = (await connection.QueryAsync( """ - SELECT COALESCE(p.platform, 'Telegram') AS Platform, - COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId, + SELECT p.platform AS Platform, + p.external_user_id AS ExternalUserId, 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.registration_status AS RegistrationStatus, sp.is_gm AS IsGm @@ -312,23 +312,13 @@ public sealed class HandleRsvpHandler( var rows = await connection.QueryAsync( """ SELECT DISTINCT - COALESCE(p.platform, 'Telegram') AS Platform, - COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId, + p.platform AS Platform, + p.external_user_id AS ExternalUserId, p.display_name AS DisplayName, - COALESCE(p.external_username, p.telegram_username) AS ExternalUsername + p.external_username AS ExternalUsername FROM group_managers gm JOIN players p ON p.id = gm.player_id 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 }, transaction); diff --git a/src/GmRelay.Shared/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs b/src/GmRelay.Shared/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs index c58ab34..47ab3d9 100644 --- a/src/GmRelay.Shared/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs +++ b/src/GmRelay.Shared/Features/Confirmation/SendConfirmation/SendConfirmationHandler.cs @@ -45,10 +45,10 @@ public sealed class SendConfirmationHandler( s.title, s.scheduled_at AS ScheduledAt, s.group_id AS GroupId, - COALESCE(g.platform, 'Telegram') AS Platform, - COALESCE(g.external_group_id, g.telegram_chat_id::TEXT) AS ExternalGroupId, + g.platform AS Platform, + g.external_group_id AS ExternalGroupId, 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.notification_mode AS NotificationMode FROM sessions s @@ -65,10 +65,10 @@ public sealed class SendConfirmationHandler( var participants = (await connection.QueryAsync( """ - SELECT COALESCE(p.platform, 'Telegram') AS Platform, - COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId, + SELECT p.platform AS Platform, + p.external_user_id AS ExternalUserId, 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.registration_status AS RegistrationStatus, sp.is_gm AS IsGm diff --git a/src/GmRelay.Shared/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs b/src/GmRelay.Shared/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs index da90f19..5f550a0 100644 --- a/src/GmRelay.Shared/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs +++ b/src/GmRelay.Shared/Features/Reminders/SendJoinLink/SendJoinLinkHandler.cs @@ -47,10 +47,10 @@ public sealed class SendJoinLinkHandler( s.title, s.join_link AS JoinLink, s.scheduled_at AS ScheduledAt, - COALESCE(g.platform, 'Telegram') AS Platform, - COALESCE(g.external_group_id, g.telegram_chat_id::TEXT) AS ExternalGroupId, + g.platform AS Platform, + g.external_group_id AS ExternalGroupId, 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.notification_mode AS NotificationMode FROM sessions s @@ -58,14 +58,14 @@ public sealed class SendJoinLinkHandler( WHERE s.id = @SessionId AND s.status = @Confirmed AND ( - (COALESCE(g.platform, 'Telegram') = 'Telegram' AND s.link_message_id IS NULL) + (g.platform = 'Telegram' AND s.link_message_id IS NULL) OR ( - COALESCE(g.platform, 'Telegram') <> 'Telegram' + g.platform <> 'Telegram' AND NOT EXISTS ( SELECT 1 FROM platform_messages pm WHERE pm.session_id = s.id - AND pm.platform = COALESCE(g.platform, 'Telegram') + AND pm.platform = g.platform AND pm.purpose = 'join_link' ) ) @@ -81,10 +81,10 @@ public sealed class SendJoinLinkHandler( var players = (await connection.QueryAsync( """ - SELECT COALESCE(p.platform, 'Telegram') AS Platform, - COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId, + SELECT p.platform AS Platform, + p.external_user_id AS ExternalUserId, 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.registration_status AS RegistrationStatus, sp.is_gm AS IsGm diff --git a/src/GmRelay.Shared/Features/Reminders/SendOneHourReminder/SendOneHourReminderHandler.cs b/src/GmRelay.Shared/Features/Reminders/SendOneHourReminder/SendOneHourReminderHandler.cs index 05702c3..a31d01b 100644 --- a/src/GmRelay.Shared/Features/Reminders/SendOneHourReminder/SendOneHourReminderHandler.cs +++ b/src/GmRelay.Shared/Features/Reminders/SendOneHourReminder/SendOneHourReminderHandler.cs @@ -56,10 +56,10 @@ public sealed class SendOneHourReminderHandler( var recipients = (await connection.QueryAsync( """ - SELECT COALESCE(p.platform, 'Telegram') AS Platform, - COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId, + SELECT p.platform AS Platform, + p.external_user_id AS ExternalUserId, p.display_name AS DisplayName, - COALESCE(p.external_username, p.telegram_username) AS ExternalUsername + p.external_username AS ExternalUsername FROM session_participants sp JOIN players p ON p.id = sp.player_id WHERE sp.session_id = @SessionId diff --git a/src/GmRelay.Shared/Features/Sessions/CreateSession/JoinSessionHandler.cs b/src/GmRelay.Shared/Features/Sessions/CreateSession/JoinSessionHandler.cs index 1724028..5a297ad 100644 --- a/src/GmRelay.Shared/Features/Sessions/CreateSession/JoinSessionHandler.cs +++ b/src/GmRelay.Shared/Features/Sessions/CreateSession/JoinSessionHandler.cs @@ -40,30 +40,19 @@ public sealed class JoinSessionHandler( { // 1. Убеждаемся, что игрок есть в базе 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( - @"INSERT INTO players (telegram_id, display_name, telegram_username, platform, external_user_id, external_username) - VALUES (@LegacyTelegramId, @Name, @LegacyTelegramUsername, @Platform, @ExternalUserId, @ExternalUsername) + @"INSERT INTO players (display_name, platform, external_user_id, external_username) + VALUES (@Name, @Platform, @ExternalUserId, @ExternalUsername) 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, - telegram_username = COALESCE(EXCLUDED.telegram_username, players.telegram_username), - platform = EXCLUDED.platform, - external_user_id = EXCLUDED.external_user_id, external_username = EXCLUDED.external_username RETURNING id;", new { - LegacyTelegramId = legacyTelegramId, Name = command.User.DisplayName, - LegacyTelegramUsername = legacyTelegramUsername, Platform = platform, command.User.ExternalUserId, command.User.ExternalUsername @@ -155,7 +144,7 @@ public sealed class JoinSessionHandler( var batchParticipants = await connection.QueryAsync( @"SELECT sp.session_id as SessionId, p.display_name as DisplayName, - COALESCE(p.external_username, p.telegram_username) as TelegramUsername, + p.external_username as TelegramUsername, sp.registration_status as RegistrationStatus FROM session_participants sp JOIN players p ON sp.player_id = p.id diff --git a/src/GmRelay.Shared/Features/Sessions/CreateSession/LeaveSessionHandler.cs b/src/GmRelay.Shared/Features/Sessions/CreateSession/LeaveSessionHandler.cs index 86cdf8e..5353c19 100644 --- a/src/GmRelay.Shared/Features/Sessions/CreateSession/LeaveSessionHandler.cs +++ b/src/GmRelay.Shared/Features/Sessions/CreateSession/LeaveSessionHandler.cs @@ -173,7 +173,7 @@ public sealed class LeaveSessionHandler( """ SELECT sp.session_id AS SessionId, p.display_name AS DisplayName, - COALESCE(p.external_username, p.telegram_username) AS TelegramUsername, + p.external_username AS TelegramUsername, sp.registration_status AS RegistrationStatus FROM session_participants sp JOIN players p ON sp.player_id = p.id diff --git a/src/GmRelay.Shared/Features/Sessions/RescheduleSession/RescheduleVotingFinalizer.cs b/src/GmRelay.Shared/Features/Sessions/RescheduleSession/RescheduleVotingFinalizer.cs index bb00f4a..a1ac156 100644 --- a/src/GmRelay.Shared/Features/Sessions/RescheduleSession/RescheduleVotingFinalizer.cs +++ b/src/GmRelay.Shared/Features/Sessions/RescheduleSession/RescheduleVotingFinalizer.cs @@ -78,8 +78,8 @@ public sealed class RescheduleVotingFinalizer( """ SELECT p.id AS PlayerId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, - p.telegram_id AS TelegramId + p.external_username AS TelegramUsername, + p.external_user_id::BIGINT AS TelegramId FROM session_participants sp JOIN players p ON p.id = sp.player_id WHERE sp.session_id = @SessionId @@ -107,7 +107,7 @@ public sealed class RescheduleVotingFinalizer( SELECT rov.option_id AS OptionId, p.id AS PlayerId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername + p.external_username AS TelegramUsername FROM reschedule_option_votes rov JOIN players p ON p.id = rov.player_id WHERE rov.proposal_id = @ProposalId diff --git a/src/GmRelay.Web/Components/Layout/NavMenu.razor b/src/GmRelay.Web/Components/Layout/NavMenu.razor index 6ef2789..d93b928 100644 --- a/src/GmRelay.Web/Components/Layout/NavMenu.razor +++ b/src/GmRelay.Web/Components/Layout/NavMenu.razor @@ -73,7 +73,7 @@ - + diff --git a/src/GmRelay.Web/Services/CalendarSubscriptionService.cs b/src/GmRelay.Web/Services/CalendarSubscriptionService.cs index a3f72af..2d55169 100644 --- a/src/GmRelay.Web/Services/CalendarSubscriptionService.cs +++ b/src/GmRelay.Web/Services/CalendarSubscriptionService.cs @@ -12,7 +12,8 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource) public string GenerateToken() => Guid.NewGuid().ToString("N"); public async Task CreateSubscriptionAsync( - long userTelegramId, + string userPlatform, + string userExternalId, Guid? groupId, CalendarSubscriptionFilter filter, CancellationToken ct = default) @@ -20,9 +21,9 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource) var token = GenerateToken(); await using var connection = await dataSource.OpenConnectionAsync(ct); await connection.ExecuteAsync( - @"INSERT INTO calendar_subscriptions (id, token, user_telegram_id, group_id, filter_type, created_at, expires_at) - VALUES (gen_random_uuid(), @token, @userTelegramId, @groupId, @filterType, now(), NULL)", - new { token, userTelegramId, groupId, filterType = (int)filter }); + @"INSERT INTO calendar_subscriptions (id, token, user_platform, user_external_id, group_id, filter_type, created_at, expires_at) + VALUES (gen_random_uuid(), @token, @userPlatform, @userExternalId, @groupId, @filterType, now(), NULL)", + new { token, userPlatform, userExternalId, groupId, filterType = (int)filter }); return token; } @@ -31,7 +32,7 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource) await using var connection = await dataSource.OpenConnectionAsync(ct); var subscription = await connection.QueryFirstOrDefaultAsync( - @"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 WHERE token = @token AND (expires_at IS NULL OR expires_at > now())", @@ -88,6 +89,6 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource) .Replace("\n", "\\n") .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); } diff --git a/src/GmRelay.Web/Services/SessionService.cs b/src/GmRelay.Web/Services/SessionService.cs index 4d0198c..68eb9b8 100644 --- a/src/GmRelay.Web/Services/SessionService.cs +++ b/src/GmRelay.Web/Services/SessionService.cs @@ -121,7 +121,7 @@ public sealed class SessionService( GROUP BY gm.group_id ) SELECT g.id, - g.telegram_chat_id AS TelegramChatId, + g.external_group_id::BIGINT AS TelegramChatId, g.external_group_id AS ExternalGroupId, COALESCE(NULLIF(g.name, g.external_group_id), latest_session.title, g.name) AS Name, g.platform AS Platform, @@ -151,7 +151,7 @@ public sealed class SessionService( return await conn.QuerySingleOrDefaultAsync( """ SELECT g.id, - g.telegram_chat_id AS TelegramChatId, + g.external_group_id::BIGINT AS TelegramChatId, g.external_group_id AS ExternalGroupId, COALESCE(NULLIF(g.name, g.external_group_id), latest_session.title, g.name) AS Name, g.platform AS Platform, @@ -213,11 +213,11 @@ public sealed class SessionService( await using var conn = await dataSource.OpenConnectionAsync(); return (await conn.QueryAsync( """ - SELECT COALESCE(p.telegram_id, 0) AS TelegramId, - COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId, + SELECT COALESCE(p.external_user_id::BIGINT, 0) AS TelegramId, + p.external_user_id AS ExternalUserId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, - COALESCE(p.external_username, p.telegram_username) AS ExternalUsername, + p.external_username AS TelegramUsername, + p.external_username AS ExternalUsername, gm.role AS Role, gm.created_at AS AddedAt FROM group_managers gm @@ -238,7 +238,7 @@ public sealed class SessionService( SELECT p.id AS PlayerId, 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 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, @@ -257,7 +257,7 @@ public sealed class SessionService( WHERE s.group_id = @GroupId AND s.scheduled_at <= now() 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 """, new { GroupId = groupId })).ToList(); @@ -356,7 +356,7 @@ public sealed class SessionService( return (await conn.QueryAsync( @"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, - g.telegram_chat_id AS TelegramChatId, + g.external_group_id::BIGINT AS TelegramChatId, s.max_players AS MaxPlayers, COALESCE(active_counts.count, 0)::int AS ActivePlayerCount, COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount, @@ -394,7 +394,7 @@ public sealed class SessionService( return await conn.QuerySingleOrDefaultAsync( @"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, - g.telegram_chat_id AS TelegramChatId, + g.external_group_id::BIGINT AS TelegramChatId, s.max_players AS MaxPlayers, COALESCE(active_counts.count, 0)::int AS ActivePlayerCount, COALESCE(waitlist_counts.count, 0)::int AS WaitlistedPlayerCount, @@ -453,7 +453,7 @@ public sealed class SessionService( var oldSession = await conn.QuerySingleOrDefaultAsync( @"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, - g.telegram_chat_id AS TelegramChatId, + g.external_group_id::BIGINT AS TelegramChatId, s.max_players AS MaxPlayers, 0 AS ActivePlayerCount, 0 AS WaitlistedPlayerCount, @@ -539,7 +539,7 @@ public sealed class SessionService( var session = await conn.QuerySingleOrDefaultAsync( @"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, - g.telegram_chat_id AS TelegramChatId, + g.external_group_id::BIGINT AS TelegramChatId, s.max_players AS MaxPlayers, 0 AS ActivePlayerCount, 0 AS WaitlistedPlayerCount, @@ -638,11 +638,11 @@ public sealed class SessionService( return (await conn.QueryAsync( """ SELECT sp.id AS Id, - COALESCE(p.telegram_id, 0) AS TelegramId, - COALESCE(p.external_user_id, p.telegram_id::TEXT) AS ExternalUserId, + COALESCE(p.external_user_id::BIGINT, 0) AS TelegramId, + p.external_user_id AS ExternalUserId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, - COALESCE(p.external_username, p.telegram_username) AS ExternalUsername, + p.external_username AS TelegramUsername, + p.external_username AS ExternalUsername, sp.rsvp_status AS RsvpStatus, sp.registration_status AS RegistrationStatus, sp.is_gm AS IsGm, @@ -665,7 +665,7 @@ public sealed class SessionService( var session = await conn.QuerySingleOrDefaultAsync( @"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, - g.telegram_chat_id AS TelegramChatId, + g.external_group_id::BIGINT AS TelegramChatId, s.max_players AS MaxPlayers, 0 AS ActivePlayerCount, 0 AS WaitlistedPlayerCount, @@ -686,9 +686,9 @@ public sealed class SessionService( var participant = await conn.QuerySingleOrDefaultAsync( """ SELECT sp.id AS Id, - p.telegram_id AS TelegramId, + p.external_user_id::BIGINT AS TelegramId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, + p.external_username AS TelegramUsername, sp.rsvp_status AS RsvpStatus, sp.registration_status AS RegistrationStatus, sp.is_gm AS IsGm, @@ -871,7 +871,7 @@ public sealed class SessionService( s.status AS Status, s.max_players AS MaxPlayers, 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.topic_created_by_bot AS TopicCreatedByBot, s.notification_mode AS NotificationMode @@ -955,7 +955,7 @@ public sealed class SessionService( s.status AS Status, s.max_players AS MaxPlayers, 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.topic_created_by_bot AS TopicCreatedByBot, s.notification_mode AS NotificationMode @@ -1177,7 +1177,7 @@ public sealed class SessionService( } var group = await conn.QuerySingleOrDefaultAsync( - "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 }, transaction); @@ -1248,7 +1248,7 @@ public sealed class SessionService( { return (await conn.QueryAsync( """ - SELECT p.telegram_id AS TelegramId, + SELECT p.external_user_id::BIGINT AS TelegramId, p.display_name AS DisplayName FROM session_participants sp JOIN players p ON p.id = sp.player_id @@ -1265,7 +1265,7 @@ public sealed class SessionService( { return (await conn.QueryAsync( """ - SELECT DISTINCT p.telegram_id AS TelegramId, + SELECT DISTINCT p.external_user_id::BIGINT AS TelegramId, p.display_name AS DisplayName FROM session_participants sp JOIN players p ON p.id = sp.player_id @@ -1318,7 +1318,7 @@ public sealed class SessionService( var participants = (await conn.QueryAsync( @"SELECT sp.session_id AS SessionId, p.display_name AS DisplayName, - p.telegram_username AS TelegramUsername, + p.external_username AS TelegramUsername, sp.registration_status AS RegistrationStatus FROM session_participants sp JOIN players p ON sp.player_id = p.id @@ -1355,7 +1355,7 @@ public sealed class SessionService( s.group_id AS GroupId, (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, - 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.thread_id ORDER BY s.scheduled_at))[1] AS ThreadId, (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 WHERE s.batch_id = @BatchId 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 }, transaction); diff --git a/tests/GmRelay.Bot.Tests/Infrastructure/Database/PlatformIdentityMigrationTests.cs b/tests/GmRelay.Bot.Tests/Infrastructure/Database/PlatformIdentityMigrationTests.cs index 450b823..0c2d1e3 100644 --- a/tests/GmRelay.Bot.Tests/Infrastructure/Database/PlatformIdentityMigrationTests.cs +++ b/tests/GmRelay.Bot.Tests/Infrastructure/Database/PlatformIdentityMigrationTests.cs @@ -117,6 +117,28 @@ public sealed class PlatformIdentityMigrationTests 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 ReadRepositoryFileAsync(string relativePath) { var directory = new DirectoryInfo(AppContext.BaseDirectory);