diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 472553c..b2762c4 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -6,7 +6,7 @@ on: - main env: - VERSION: 3.2.0 + VERSION: 3.3.0 jobs: # ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами) diff --git a/Directory.Build.props b/Directory.Build.props index 49062e2..ae7048f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 3.2.0 + 3.3.0 net10.0 preview enable diff --git a/README.md b/README.md index 51a0430..8722327 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Проект разработан с упором на производительность, архитектуру Vertical Slice, Native AOT (для бота) и удобство развертывания с использованием .NET Aspire. -**Текущая версия:** `v2.8.0`. +**Текущая версия:** `v3.3.0`. --- @@ -37,6 +37,7 @@ - **📱 Telegram Mini App Dashboard**: Мобильная панель открывается из Telegram, проверяет `initData` на сервере, учитывает safe-area телефона и верхнюю панель Telegram. - **✏️ Редактирование**: Детальное изменение дат, названий и статусов сессий. - **🤝 Co-GM и делегирование**: Owner назначает помощников по Telegram ID; co-GM управляет расписанием, но **не может назначать других co-GM**. +- **🌍 Публичные страницы клубов**: Owner и co-GM включают read-only страницу `/club/{slug}` и отдельные ссылки `/s/{sessionId}` только для опубликованных сессий; состав игроков и приватные join-ссылки не показываются. - **📋 Шаблоны кампаний**: Вкладка `Шаблоны` отдельно от страницы группы: сохранение типовых параметров и запуск нового batch из шаблона. - **📦 Bulk-операции для Batch Sessions**: - обновить общий `title`/`link` у всей пачки; diff --git a/compose.yaml b/compose.yaml index 104c368..af34e79 100644 --- a/compose.yaml +++ b/compose.yaml @@ -49,7 +49,7 @@ services: crond -f bot: - image: git.codeanddice.ru/toutsu/gmrelay-bot:3.2.0 + image: git.codeanddice.ru/toutsu/gmrelay-bot:3.3.0 restart: always depends_on: db: @@ -67,7 +67,7 @@ services: retries: 3 discord: - image: git.codeanddice.ru/toutsu/gmrelay-discord-bot:3.2.0 + image: git.codeanddice.ru/toutsu/gmrelay-discord-bot:3.3.0 restart: always depends_on: db: @@ -84,7 +84,7 @@ services: retries: 3 web: - image: git.codeanddice.ru/toutsu/gmrelay-web:3.2.0 + image: git.codeanddice.ru/toutsu/gmrelay-web:3.3.0 restart: always depends_on: db: diff --git a/docs/c4-system-context.md b/docs/c4-system-context.md index 036372c..774c8f4 100644 --- a/docs/c4-system-context.md +++ b/docs/c4-system-context.md @@ -8,8 +8,9 @@ C4Context Person(gm, "Game Master", "Creates sessions and manages schedules") Person(player, "Player", "Joins, leaves, confirms, and receives reminders") + Person(visitor, "Public visitor", "Views published club schedules without private player data") - System(gmrelay, "GM-Relay", "Telegram bot, Discord worker, web dashboard, and shared scheduling logic") + System(gmrelay, "GM-Relay", "Telegram bot, Discord worker, web dashboard, public club pages, and shared scheduling logic") 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") @@ -19,6 +20,7 @@ C4Context Rel(gm, discord, "Uses /newsession and /listsessions") Rel(player, telegram, "Uses inline buttons") Rel(player, discord, "Uses Join/Leave and RSVP buttons") + Rel(visitor, gmrelay, "Views public club and session pages") Rel(telegram, gmrelay, "Updates via long polling") Rel(discord, gmrelay, "Gateway events and component interactions") Rel(gmrelay, telegram, "SendMessage, EditMessage, AnswerCallbackQuery") @@ -34,13 +36,14 @@ C4Container Person(gm, "Game Master") Person(player, "Player") + Person(visitor, "Public visitor") 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, "Discord Gateway Worker", "Внутри GmRelay.Bot", "NetCord Gateway, slash commands, scheduler notifications, button interactions, healthcheck :8082") - Container(web, "GmRelay.Web", "Blazor Server", "Dashboard, Mini App pages, editing and stats") + Container(web, "GmRelay.Web", "Blazor Server", "Dashboard, Mini App pages, public club pages, editing and stats") Container(shared, "GmRelay.Shared", ".NET library", "Shared domain models, rendering, scheduler, and platform-neutral handlers") - ContainerDb(db, "PostgreSQL", "Database", "sessions, players, session_participants, game_groups, platform identities") + ContainerDb(db, "PostgreSQL", "Database", "sessions, players, session_participants, game_groups, publication settings, platform identities") } System_Ext(telegram, "Telegram Bot API") @@ -50,6 +53,7 @@ C4Container Rel(gm, discord, "Slash commands") Rel(player, telegram, "Callback queries") Rel(player, discord, "Button interactions") + Rel(visitor, web, "Read-only public schedule pages") Rel(telegram, bot, "GetUpdates") Rel(discord, discordBot, "Gateway events") Rel(bot, telegram, "Bot API calls") diff --git a/src/GmRelay.Bot/Migrations/V026__add_public_club_pages.sql b/src/GmRelay.Bot/Migrations/V026__add_public_club_pages.sql new file mode 100644 index 0000000..d1f667a --- /dev/null +++ b/src/GmRelay.Bot/Migrations/V026__add_public_club_pages.sql @@ -0,0 +1,17 @@ +-- Public club pages and read-only schedule publication controls. + +ALTER TABLE game_groups + ADD COLUMN public_slug VARCHAR(120), + ADD COLUMN public_schedule_enabled BOOLEAN NOT NULL DEFAULT false, + ADD COLUMN public_schedule_updated_at TIMESTAMPTZ; + +ALTER TABLE sessions + ADD COLUMN is_public BOOLEAN NOT NULL DEFAULT false; + +CREATE UNIQUE INDEX ux_game_groups_public_slug + ON game_groups (lower(public_slug)) + WHERE public_slug IS NOT NULL; + +CREATE INDEX ix_sessions_public_schedule + ON sessions (group_id, scheduled_at) + WHERE is_public = true AND status <> 'Cancelled'; diff --git a/src/GmRelay.Web/Components/Layout/NavMenu.razor b/src/GmRelay.Web/Components/Layout/NavMenu.razor index 106c43a..3548a4f 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/Components/Layout/PublicLayout.razor b/src/GmRelay.Web/Components/Layout/PublicLayout.razor new file mode 100644 index 0000000..76606bf --- /dev/null +++ b/src/GmRelay.Web/Components/Layout/PublicLayout.razor @@ -0,0 +1,21 @@ +@inherits LayoutComponentBase + +
+
+ + GM-Relay + GM-Relay + + Войти +
+ +
+ @Body +
+
+ +
+ Произошла непредвиденная ошибка. + Перезагрузить + × +
diff --git a/src/GmRelay.Web/Components/Pages/GroupDetails.razor b/src/GmRelay.Web/Components/Pages/GroupDetails.razor index 4555f65..371c8cc 100644 --- a/src/GmRelay.Web/Components/Pages/GroupDetails.razor +++ b/src/GmRelay.Web/Components/Pages/GroupDetails.razor @@ -72,6 +72,58 @@ } + @if (publicSettings is not null) + { +
+
+
+

Публичная страница клуба

+

@publicSettings.PublicSessionCount опубликованных игр без состава игроков и приватных ссылок

+
+ + @(publicSettings.PublicScheduleEnabled ? "Включена" : "Выключена") + +
+ + +
+
+ +
Если выключено, публичная страница и ссылки на сессии недоступны.
+
+
+ + +
Латиница, цифры и дефисы, например `night-city-club`.
+
+
+ +
+ + @if (PublicClubUrl is not null && publicSettings.PublicScheduleEnabled) + { + + Открыть публичную страницу + + } +
+
+ + @if (PublicClubUrl is not null && publicSettings.PublicScheduleEnabled) + { + + } +
+ } + @if (!string.IsNullOrEmpty(errorMessage)) {
@@ -201,6 +253,17 @@ +
+ 0 ? "status-warning" : "status-neutral")"> + @FormatBatchPublication(batch) + + +
+