docs(#22): обновить Архитектуру — neutral rendering, v1.10.0
@@ -6,102 +6,52 @@ GM-Relay состоит из нескольких .NET-проектов и об
|
|||||||
|
|
||||||
- `src/GmRelay.AppHost` — Aspire AppHost. Поднимает PostgreSQL с PgAdmin и запускает bot/web проекты.
|
- `src/GmRelay.AppHost` — Aspire AppHost. Поднимает PostgreSQL с PgAdmin и запускает bot/web проекты.
|
||||||
- `src/GmRelay.Bot` — Worker Service с Telegram long polling, миграциями, планировщиком и вертикальными срезами бота.
|
- `src/GmRelay.Bot` — Worker Service с Telegram long polling, миграциями, планировщиком и вертикальными срезами бота.
|
||||||
- `src/GmRelay.Web` — Blazor Server UI для ГМа, включая Telegram Login Widget и Telegram Mini App вход.
|
- `src/GmRelay.Web` — Blazor Server UI для ГМа, включая Telegram Login Widget и Telegram Mini App.
|
||||||
- `src/GmRelay.Shared` — общие доменные значения и rendering-код Telegram-сообщений.
|
- `src/GmRelay.Shared` — чисто нейтральные доменные типы, DTO и platform-agnostic рендеринг. **НЕ зависит от Telegram.Bot**.
|
||||||
- `src/GmRelay.ServiceDefaults` — общие Aspire defaults: OpenTelemetry, service discovery, resilience, health checks.
|
- `src/GmRelay.ServiceDefaults` — Aspire defaults: OpenTelemetry, service discovery, resilience, health checks.
|
||||||
|
|
||||||
## Архитектурное решение
|
## Вертикальная архитектура
|
||||||
|
|
||||||
В проекте принят подход Vertical Slice Architecture. Логика бота лежит в `src/GmRelay.Bot/Features/*`, где каждый сценарий содержит свой handler и локальные DTO.
|
Все use-cases живут в `src/GmRelay.Bot/Features/*/`. Каждый фича-срез содержит свои handler, DTO и тесты. Зависимости идут только к `Shared`. См. `docs/adr/0001-use-vertical-slice-native-aot-and-aspire.md`.
|
||||||
|
|
||||||
Причины такого выбора зафиксированы в `docs/adr/0001-use-vertical-slice-native-aot-and-aspire.md`:
|
## Telegram updates
|
||||||
|
|
||||||
- сценариев немного, отдельный Clean Architecture слой был бы лишним;
|
`TelegramBotService` получает updates через long polling и маршрутизирует их в `UpdateRouter`.
|
||||||
- зависимости Npgsql и Telegram.Bot стабильны;
|
|
||||||
- Native AOT требует явной регистрации зависимостей и аккуратного SQL-маппинга;
|
|
||||||
- BackgroundService + PeriodicTimer проще и надёжнее Quartz.NET для периодических проверок расписания и дедлайнов голосования.
|
|
||||||
|
|
||||||
## Поток Telegram updates
|
Команды и callback:
|
||||||
|
- `/start`, `/help`, `/newsession`, `/listsessions`, `/exportcalendar`
|
||||||
|
- `join_session:<sessionId>`, `leave_session:<sessionId>`, `cancel_session:<sessionId>`
|
||||||
|
- `reschedule_session:<sessionId>`, `reschedule_vote:<optionId>`, `rsvp:<confirm|decline>:<sessionId>`
|
||||||
|
|
||||||
`TelegramBotService` получает updates через long polling и передаёт их в `UpdateRouter`.
|
## Рендеринг батчей (v1.10.0+)
|
||||||
|
|
||||||
`UpdateRouter` явно маршрутизирует:
|
Рендеринг разделён на platform-neutral View Builder и platform-specific Renderers:
|
||||||
|
|
||||||
- `/start`, `/help`, `/newsession`, `/listsessions`, `/exportcalendar`;
|
- `SessionBatchViewBuilder` (`GmRelay.Shared.Rendering`) — собирает нейтральную view model (`SessionBatchViewModel`) из доменных DTO. Не зависит от Telegram.
|
||||||
- callback `join_session:<sessionId>`;
|
- `TelegramSessionBatchRenderer` (`GmRelay.Bot.Infrastructure.Telegram` / `GmRelay.Web.Services`) — рендерит HTML + `InlineKeyboardMarkup` для Telegram.
|
||||||
- callback `leave_session:<sessionId>`;
|
- `DiscordSessionBatchRenderer` (`GmRelay.Shared.Rendering`) — заглушка для Discord renderer (issue #26).
|
||||||
- callback `cancel_session:<sessionId>`;
|
- `BatchMessageEditor` (`GmRelay.Bot` / `GmRelay.Web`) — редактирует batch-сообщения в Telegram (текст или caption).
|
||||||
- callback `delete_session:<sessionId>`;
|
|
||||||
- callback `reschedule_session:<sessionId>`;
|
|
||||||
- callback `reschedule_vote:<optionId>`;
|
|
||||||
- callback `rsvp:<confirm|decline>:<sessionId>`;
|
|
||||||
- обычные текстовые сообщения ГМа как ввод 2-3 вариантов времени и дедлайна для активного переноса.
|
|
||||||
|
|
||||||
`/newsession` может прийти как обычный text message или как caption к прикреплённому фото. Вариант с caption используется для обложек batch-поста: `CreateSessionHandler` отправляет фото отдельным сообщением перед текстовым расписанием, чтобы обновления состава продолжали редактировать обычный `batch_message_id`.
|
Цепочка использования:
|
||||||
|
```csharp
|
||||||
Маршрутизация не использует reflection или assembly scanning.
|
var view = SessionBatchViewBuilder.Build(title, sessions, participants);
|
||||||
|
var (text, markup) = TelegramSessionBatchRenderer.Render(view);
|
||||||
## Планировщик
|
```
|
||||||
|
|
||||||
`SessionSchedulerService` — stateless background service. Он запускается сразу при старте, затем раз в минуту:
|
|
||||||
|
|
||||||
1. Ищет сессии `Planned`, у которых `scheduled_at - 24h <= now()`.
|
|
||||||
2. Вызывает `SendConfirmationHandler` и переводит сессию в `ConfirmationSent`.
|
|
||||||
3. Ищет сессии `Confirmed` или `ConfirmationSent`, у которых `scheduled_at - 1h <= now()` и `one_hour_reminder_processed_at IS NULL`.
|
|
||||||
4. Вызывает `SendOneHourReminderHandler`, который учитывает `sessions.notification_mode`.
|
|
||||||
5. Ищет сессии `Confirmed`, у которых `scheduled_at - 5min <= now()` и `link_message_id IS NULL`.
|
|
||||||
6. Вызывает `SendJoinLinkHandler`.
|
|
||||||
|
|
||||||
Состояние хранится в PostgreSQL, поэтому после рестарта сервис продолжает работу без in-memory очередей.
|
|
||||||
|
|
||||||
`RescheduleVotingDeadlineService` — отдельный stateless background service. Он раз в минуту ищет `reschedule_proposals` в статусе `Voting`, у которых `voting_deadline_at <= now()`, выбирает вариант с наибольшим числом голосов, применяет перенос или отклоняет голосование при ничьей/нуле голосов, обновляет Telegram-сообщения и сбрасывает RSVP при успешном переносе.
|
|
||||||
|
|
||||||
## Web UI
|
## Web UI
|
||||||
|
|
||||||
Blazor Server-приложение использует cookie auth и Telegram Login Widget.
|
Blazor Server-side с cookie auth и Telegram Login Widget.
|
||||||
Начиная с `1.9.0`, тот же Web UI открывается как Telegram Mini App через `/miniapp`; в `1.9.9` основной вход по-прежнему проверяет Telegram `initData`, а fallback `/login` использует callback-mode Telegram Login Widget и endpoint `/auth/telegram-login`, чтобы cookie выставлялась внутри активного Mini App WebView без ручного закрытия и повторного открытия.
|
|
||||||
|
|
||||||
Основные сервисы:
|
Ключевые endpoints:
|
||||||
|
- `/login` — Telegram Login Widget
|
||||||
|
- `/miniapp` — Telegram Mini App WebView
|
||||||
|
- `/auth/telegram-login` — callback endpoint для Telegram Login Widget
|
||||||
|
- `/auth/status` — проверка статуса аутентификации
|
||||||
|
- `/` — дашборд (только owner и co-GM)
|
||||||
|
- `/templates` — шаблоны кампаний
|
||||||
|
- `/group/{GroupId}` — сессии группы, bulk-операции
|
||||||
|
- `/session/edit/{SessionId}` — редактирование сессии
|
||||||
|
|
||||||
- `TelegramAuthService` валидирует HMAC-SHA256 подпись Telegram Login Widget, Telegram WebApp `initData` и `auth_date`.
|
## C4-диаграммы
|
||||||
- `SessionService` читает и обновляет группы/сессии через Dapper, включая шаблоны кампаний и bulk-операции по `batch_id`.
|
|
||||||
- `SessionService` сохраняет режим уведомлений batch и дублирует Web-уведомления в ЛС, если включён `GroupAndDirect`.
|
|
||||||
- `SessionService` читает `group_managers`, проверяет роли `Owner`/`CoGm` и сохраняет назначения co-GM.
|
|
||||||
- `BatchSchedulePlanner` задаёт детерминированные правила fixed-interval переноса, повторяющихся расписаний и clone-смещения batch.
|
|
||||||
- `AuthorizedSessionService` проверяет, что текущий Telegram ID является owner или co-GM группы; owner-only операции делегирования проходят через отдельную проверку роли.
|
|
||||||
|
|
||||||
Основные страницы:
|
Хранятся в `docs/c4-system-context.md`. Описывают system context, container view и component view для `GmRelay.Bot`.
|
||||||
|
|
||||||
- `/login` — вход через Telegram.
|
|
||||||
- `/miniapp` — вход из Telegram Mini App через серверную проверку `initData`.
|
|
||||||
- `/auth/telegram-login` — callback-вход Telegram Login Widget: принимает JSON payload, проверяет Telegram HMAC и выдаёт cookie-сессию внутри текущего WebView.
|
|
||||||
- `/auth/status` — лёгкая проверка текущей cookie-сессии для Mini App fallback-flow.
|
|
||||||
- `/` — список групп, где пользователь owner или co-GM.
|
|
||||||
- `/templates` — отдельная вкладка управления шаблонами кампаний по доступным группам.
|
|
||||||
- `/group/{GroupId}` — управление группой, применение шаблонов кампаний, будущие сессии и batch bulk-операции.
|
|
||||||
- `/session/edit/{SessionId}` — редактирование названия, времени и ссылки.
|
|
||||||
|
|
||||||
Поток Mini App:
|
|
||||||
|
|
||||||
1. Бот открывает URL `Telegram:MiniAppUrl` через menu button или inline WebApp-кнопку `/start`.
|
|
||||||
2. Страница `/miniapp` ждёт `Telegram.WebApp.initData` из Telegram JS API.
|
|
||||||
3. Blazor отправляет initData на `/auth/telegram-webapp`.
|
|
||||||
4. Если `initData` недоступен, fallback `/login` регистрирует `data-onauth`, получает Telegram Login Widget payload в JavaScript и отправляет его на `/auth/telegram-login` без внешнего redirect-flow.
|
|
||||||
5. Shell вызывает `Telegram.WebApp.ready()` / `expand()`, синхронизирует `safeAreaInset` и `contentSafeAreaInset` в CSS-переменные и слушает `safeAreaChanged`, `contentSafeAreaChanged`, `viewportChanged`.
|
|
||||||
6. Сервер проверяет подпись через ключ `WebAppData` + токен бота, извлекает Telegram ID из `user` и создаёт стандартную auth-cookie.
|
|
||||||
7. Дальше пользователь работает с теми же страницами и сервисами, что и в обычном Web Dashboard.
|
|
||||||
|
|
||||||
## Общий домен
|
|
||||||
|
|
||||||
`GmRelay.Shared` содержит:
|
|
||||||
|
|
||||||
- `SessionStatus`: `Planned`, `ConfirmationSent`, `Confirmed`, `Cancelled`.
|
|
||||||
- `RsvpStatus`: `Pending`, `Confirmed`, `Declined`.
|
|
||||||
- `SessionNotificationMode`: `GroupAndDirect`, `GroupOnly`.
|
|
||||||
- `GroupManagerRole`: `Owner`, `CoGm`.
|
|
||||||
- `MoscowTime`: парсинг и форматирование МСК (`UTC+3`).
|
|
||||||
- `SessionBatchRenderer`: единый renderer для Telegram-сообщения пачки сессий.
|
|
||||||
|
|
||||||
## Диаграммы
|
|
||||||
|
|
||||||
C4-диаграммы хранятся в `docs/c4-system-context.md`. Там описаны system context, container view и component view для `GmRelay.Bot`.
|
|
||||||
Reference in New Issue
Block a user