Merge pull request #104: refactor: завершить platform migration и удалить deprecated telegram_* scaffolding
- Migrated all core domain SQL from telegram_* columns to platform + external_* - Added V024/V025 migrations with backfill and deprecation comments - Removed all COALESCE(external_*, telegram_*) fallbacks - Replaced gm_telegram_id join with group_managers query in HandleRsvpHandler - Updated Bot, Web, DiscordBot, and Shared handlers - Bumped version to 3.1.0 CI run #265 passed (289 tests, 0 warnings, 0 vulnerabilities) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
VERSION: 3.0.10
|
||||
VERSION: 3.1.0
|
||||
|
||||
jobs:
|
||||
# ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>3.0.10</Version>
|
||||
<Version>3.1.0</Version>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
+3
-3
@@ -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:
|
||||
|
||||
@@ -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<DirectNotificationRecipient>(
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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<SessionCreationGroupAccessDto>(
|
||||
@@ -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<Guid>(
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<Guid?>(
|
||||
@"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";
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+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,
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
|
||||
@@ -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>(
|
||||
@"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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<ParticipantRsvpRow>(
|
||||
"""
|
||||
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<RsvpRecipientRow>(
|
||||
"""
|
||||
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);
|
||||
|
||||
+6
-6
@@ -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<ConfirmationParticipantRow>(
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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<JoinLinkPlayerRow>(
|
||||
"""
|
||||
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
|
||||
|
||||
+3
-3
@@ -56,10 +56,10 @@ public sealed class SendOneHourReminderHandler(
|
||||
|
||||
var recipients = (await connection.QueryAsync<OneHourReminderRecipientRow>(
|
||||
"""
|
||||
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
|
||||
|
||||
@@ -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<Guid>(
|
||||
@"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<ParticipantBatchDto>(
|
||||
@"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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="nav-version">v3.0.10</div>
|
||||
<div class="nav-version">v3.1.0</div>
|
||||
</div>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
|
||||
@@ -12,7 +12,8 @@ public sealed class CalendarSubscriptionService(NpgsqlDataSource dataSource)
|
||||
public string GenerateToken() => Guid.NewGuid().ToString("N");
|
||||
|
||||
public async Task<string> 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<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
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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<WebGameGroup>(
|
||||
"""
|
||||
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<WebGroupManager>(
|
||||
"""
|
||||
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<WebSession>(
|
||||
@"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<WebSession>(
|
||||
@"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<WebSession>(
|
||||
@"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<WebSession>(
|
||||
@"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<WebParticipant>(
|
||||
"""
|
||||
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<WebSession>(
|
||||
@"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<WebParticipant>(
|
||||
"""
|
||||
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<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 },
|
||||
transaction);
|
||||
|
||||
@@ -1248,7 +1248,7 @@ public sealed class SessionService(
|
||||
{
|
||||
return (await conn.QueryAsync<WebDirectNotificationRecipient>(
|
||||
"""
|
||||
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<WebDirectNotificationRecipient>(
|
||||
"""
|
||||
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<ParticipantBatchDto>(
|
||||
@"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);
|
||||
|
||||
@@ -62,7 +62,7 @@ public sealed class DiscordProjectStructureTests
|
||||
var prChecks = File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "pr-checks.yml"));
|
||||
var deploy = File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml"));
|
||||
|
||||
Assert.Contains("gmrelay-discord-bot:3.0.10", compose);
|
||||
Assert.Contains("gmrelay-discord-bot:3.1.0", compose);
|
||||
Assert.Contains("Discord__Token=${DISCORD_BOT_TOKEN:?Set DISCORD_BOT_TOKEN in .env}", compose);
|
||||
Assert.Contains("src/GmRelay.DiscordBot/Dockerfile", deploy);
|
||||
Assert.Contains("DISCORD_BOT_TOKEN", deploy);
|
||||
@@ -76,13 +76,13 @@ public sealed class DiscordProjectStructureTests
|
||||
{
|
||||
var repoRoot = GetRepoRoot();
|
||||
|
||||
Assert.Contains("<Version>3.0.10</Version>", File.ReadAllText(Path.Combine(repoRoot, "Directory.Build.props")));
|
||||
Assert.Contains("VERSION: 3.0.10", File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml")));
|
||||
Assert.Contains("gmrelay-bot:3.0.10", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("gmrelay-web:3.0.10", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("gmrelay-discord-bot:3.0.10", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("<Version>3.1.0</Version>", File.ReadAllText(Path.Combine(repoRoot, "Directory.Build.props")));
|
||||
Assert.Contains("VERSION: 3.1.0", File.ReadAllText(Path.Combine(repoRoot, ".gitea", "workflows", "deploy.yml")));
|
||||
Assert.Contains("gmrelay-bot:3.1.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("gmrelay-web:3.1.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains("gmrelay-discord-bot:3.1.0", File.ReadAllText(Path.Combine(repoRoot, "compose.yaml")));
|
||||
Assert.Contains(
|
||||
"v3.0.10",
|
||||
"v3.1.0",
|
||||
File.ReadAllText(Path.Combine(repoRoot, "src", "GmRelay.Web", "Components", "Layout", "NavMenu.razor")));
|
||||
}
|
||||
|
||||
|
||||
@@ -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<string> ReadRepositoryFileAsync(string relativePath)
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
|
||||
Reference in New Issue
Block a user