feat(discord): enable session join leave buttons
PR Checks / test-and-build (pull_request) Successful in 6m6s
PR Checks / test-and-build (pull_request) Successful in 6m6s
Move neutral join/leave handlers into GmRelay.Shared so Telegram and Discord share capacity, waitlist, duplicate-click, and schedule-update behavior. Add Discord component routing for join_session and leave_session buttons with deferred ephemeral replies and serialized schedule message updates. Bump version to 2.5.0 and update Discord docs. Refs #29
This commit is contained in:
+71
-39
@@ -1,4 +1,4 @@
|
||||
# GM-Relay — C4 Model
|
||||
# GM-Relay - C4 Model
|
||||
|
||||
## Level 1: System Context
|
||||
|
||||
@@ -6,19 +6,24 @@
|
||||
C4Context
|
||||
title GM-Relay System Context
|
||||
|
||||
Person(gm, "Game Master", "Создаёт сессии, управляет расписанием игр")
|
||||
Person(player, "Player", "Подтверждает участие через inline-кнопки")
|
||||
Person(gm, "Game Master", "Creates sessions and manages schedules")
|
||||
Person(player, "Player", "Joins, leaves, confirms, and receives reminders")
|
||||
|
||||
System(gmrelay, "GM-Relay Bot", "Telegram Worker Service на Raspberry Pi. Управляет подтверждениями, рассылает напоминания и ссылки.")
|
||||
System(gmrelay, "GM-Relay", "Telegram bot, Discord worker, web dashboard, and shared scheduling logic")
|
||||
|
||||
System_Ext(telegram, "Telegram Bot API", "Long Polling. Сообщения, inline keyboards, callback queries.")
|
||||
SystemDb_Ext(postgres, "PostgreSQL", "Сессии, игроки, RSVP-статусы")
|
||||
System_Ext(telegram, "Telegram Bot API", "Commands, inline keyboards, callback queries, Mini App entry points")
|
||||
System_Ext(discord, "Discord Gateway and REST API", "Slash commands, button interactions, message edits, ephemeral replies")
|
||||
SystemDb_Ext(postgres, "PostgreSQL", "Sessions, players, participants, groups, platform identities")
|
||||
|
||||
Rel(gm, telegram, "Команды бота (/newsession)")
|
||||
Rel(player, telegram, "Нажимает кнопки (✅ Буду / ❌ Не смогу)")
|
||||
Rel(telegram, gmrelay, "Updates (Long Polling)")
|
||||
Rel(gm, telegram, "Creates and manages sessions")
|
||||
Rel(gm, discord, "Uses /newsession and /listsessions")
|
||||
Rel(player, telegram, "Uses inline buttons")
|
||||
Rel(player, discord, "Uses Join/Leave buttons")
|
||||
Rel(telegram, gmrelay, "Updates via long polling")
|
||||
Rel(discord, gmrelay, "Gateway events and component interactions")
|
||||
Rel(gmrelay, telegram, "SendMessage, EditMessage, AnswerCallbackQuery")
|
||||
Rel(gmrelay, postgres, "SQL (Npgsql + Dapper)")
|
||||
Rel(gmrelay, discord, "Send/edit schedule messages and ephemeral interaction replies")
|
||||
Rel(gmrelay, postgres, "SQL via Npgsql and Dapper")
|
||||
```
|
||||
|
||||
## Level 2: Container
|
||||
@@ -30,49 +35,76 @@ C4Container
|
||||
Person(gm, "Game Master")
|
||||
Person(player, "Player")
|
||||
|
||||
System_Boundary(pi, "Raspberry Pi 5") {
|
||||
Container(bot, "GmRelay.Bot", "Worker Service, .NET 10 AOT", "Long polling, обработка команд и callback queries, планировщик")
|
||||
ContainerDb(db, "PostgreSQL 16", "Database", "sessions, players, session_participants, game_groups")
|
||||
System_Boundary(runtime, "Docker Compose / Aspire runtime") {
|
||||
Container(bot, "GmRelay.Bot", "Worker Service, .NET 10 AOT", "Telegram long polling, commands, callback routing, reminders")
|
||||
Container(discordBot, "GmRelay.DiscordBot", "Worker Service, .NET 10", "NetCord Gateway, slash commands, Join/Leave button interactions")
|
||||
Container(web, "GmRelay.Web", "Blazor Server", "Dashboard, Mini App pages, editing and stats")
|
||||
Container(shared, "GmRelay.Shared", ".NET library", "Shared domain models, rendering, and platform-neutral join/leave handlers")
|
||||
ContainerDb(db, "PostgreSQL", "Database", "sessions, players, session_participants, game_groups, platform identities")
|
||||
}
|
||||
|
||||
System_Ext(telegram, "Telegram Bot API")
|
||||
System_Ext(discord, "Discord Gateway and REST API")
|
||||
|
||||
Rel(gm, telegram, "Commands")
|
||||
Rel(player, telegram, "Callback Queries")
|
||||
Rel(telegram, bot, "GetUpdates (Long Polling)")
|
||||
Rel(gm, discord, "Slash commands")
|
||||
Rel(player, telegram, "Callback queries")
|
||||
Rel(player, discord, "Button interactions")
|
||||
Rel(telegram, bot, "GetUpdates")
|
||||
Rel(discord, discordBot, "Gateway events")
|
||||
Rel(bot, telegram, "Bot API calls")
|
||||
Rel(discordBot, discord, "REST send/edit/reply calls")
|
||||
Rel(bot, shared, "Uses shared renderers and join/leave handlers")
|
||||
Rel(discordBot, shared, "Uses shared renderers and join/leave handlers")
|
||||
Rel(web, shared, "Uses shared domain and rendering models")
|
||||
Rel(bot, db, "Npgsql + Dapper.AOT")
|
||||
Rel(discordBot, db, "Npgsql + Dapper")
|
||||
Rel(web, db, "Npgsql + Dapper")
|
||||
```
|
||||
|
||||
## Level 3: Component (GmRelay.Bot)
|
||||
## Level 3: Component - Session Interactions
|
||||
|
||||
```mermaid
|
||||
C4Component
|
||||
title GmRelay.Bot Components
|
||||
title Platform-Neutral Session Interactions
|
||||
|
||||
Container_Boundary(bot, "GmRelay.Bot") {
|
||||
Component(polling, "TelegramBotService", "BackgroundService", "Long polling loop, получает Updates")
|
||||
Component(router, "UpdateRouter", "C#", "Маршрутизирует Update → Handler по типу")
|
||||
Component(scheduler, "SessionSchedulerService", "BackgroundService", "PeriodicTimer(60s): T-24ч и T-5мин триггеры")
|
||||
Component(migrator, "DbMigrator", "DbUp", "SQL миграции при старте")
|
||||
|
||||
Component(confirm, "SendConfirmationHandler", "Feature", "Отправляет inline keyboard за 24ч")
|
||||
Component(rsvp, "HandleRsvpHandler", "Feature", "Обрабатывает ✅/❌, проверяет all-confirmed")
|
||||
Component(link, "SendJoinLinkHandler", "Feature", "Отправляет join link за 5 мин")
|
||||
Container_Boundary(shared, "GmRelay.Shared") {
|
||||
Component(join, "JoinSessionHandler", "Feature handler", "Adds players as Active or Waitlisted with session row locking")
|
||||
Component(leave, "LeaveSessionHandler", "Feature handler", "Removes players and promotes the first waitlisted player when capacity allows")
|
||||
Component(updateLock, "ScheduleMessageUpdateLock", "In-memory keyed lock", "Serializes DB changes and schedule message edits per platform message")
|
||||
Component(renderer, "SessionBatchViewBuilder", "Renderer model builder", "Builds platform-neutral schedule views and actions")
|
||||
}
|
||||
|
||||
System_Ext(telegram, "Telegram Bot API")
|
||||
ContainerDb(db, "PostgreSQL")
|
||||
Container_Boundary(discordBot, "GmRelay.DiscordBot") {
|
||||
Component(discordModule, "DiscordSessionInteractionModule", "NetCord component module", "Maps join_session/leave_session buttons to neutral commands")
|
||||
Component(discordMessenger, "DiscordPlatformMessenger", "IPlatformMessenger", "Edits Discord schedule messages and stores interaction replies")
|
||||
}
|
||||
|
||||
Rel(polling, router, "Update")
|
||||
Rel(router, rsvp, "CallbackQuery rsvp:*")
|
||||
Rel(scheduler, confirm, "T-24h trigger")
|
||||
Rel(scheduler, link, "T-5min trigger")
|
||||
Rel(confirm, telegram, "SendMessage + InlineKeyboard")
|
||||
Rel(rsvp, telegram, "EditMessage + AnswerCallback")
|
||||
Rel(link, telegram, "SendMessage + user mentions")
|
||||
Rel(confirm, db, "SELECT/UPDATE sessions")
|
||||
Rel(rsvp, db, "UPDATE participants, SELECT counts")
|
||||
Rel(link, db, "SELECT confirmed players")
|
||||
Rel(migrator, db, "DDL migrations")
|
||||
Container_Boundary(bot, "GmRelay.Bot") {
|
||||
Component(updateRouter, "UpdateRouter", "Telegram adapter", "Maps callback queries to neutral commands")
|
||||
Component(telegramMessenger, "TelegramPlatformMessenger", "IPlatformMessenger", "Edits Telegram schedule messages and answers callback queries")
|
||||
}
|
||||
|
||||
ContainerDb(db, "PostgreSQL")
|
||||
System_Ext(telegram, "Telegram Bot API")
|
||||
System_Ext(discord, "Discord Gateway and REST API")
|
||||
|
||||
Rel(discord, discordModule, "Button interaction")
|
||||
Rel(discordModule, join, "JoinSessionCommand")
|
||||
Rel(discordModule, leave, "LeaveSessionCommand")
|
||||
Rel(discordModule, discord, "Deferred ephemeral reply, then modify response")
|
||||
Rel(updateRouter, join, "JoinSessionCommand")
|
||||
Rel(updateRouter, leave, "LeaveSessionCommand")
|
||||
Rel(join, updateLock, "Acquire by PlatformMessageRef")
|
||||
Rel(leave, updateLock, "Acquire by PlatformMessageRef")
|
||||
Rel(join, db, "SELECT FOR UPDATE, INSERT participant")
|
||||
Rel(leave, db, "SELECT FOR UPDATE, DELETE/promote participant")
|
||||
Rel(join, renderer, "Build updated schedule view")
|
||||
Rel(leave, renderer, "Build updated schedule view")
|
||||
Rel(join, discordMessenger, "Update Discord schedule when command is Discord")
|
||||
Rel(leave, discordMessenger, "Update Discord schedule when command is Discord")
|
||||
Rel(join, telegramMessenger, "Update Telegram schedule when command is Telegram")
|
||||
Rel(leave, telegramMessenger, "Update Telegram schedule when command is Telegram")
|
||||
Rel(discordMessenger, discord, "ModifyMessage + ephemeral text")
|
||||
Rel(telegramMessenger, telegram, "EditMessage + AnswerCallbackQuery")
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user