6
Архитектура
Toutsu edited this page 2026-04-27 14:58:56 +03:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Архитектура

GM-Relay состоит из нескольких .NET-проектов и общей PostgreSQL БД.

Проекты

  • src/GmRelay.AppHost — Aspire AppHost. Поднимает PostgreSQL с PgAdmin и запускает bot/web проекты.
  • src/GmRelay.Bot — Worker Service с Telegram long polling, миграциями, планировщиком и вертикальными срезами бота.
  • src/GmRelay.Web — Blazor Server UI для ГМа.
  • src/GmRelay.Shared — общие доменные значения и rendering-код Telegram-сообщений.
  • src/GmRelay.ServiceDefaults — общие Aspire defaults: OpenTelemetry, service discovery, resilience, health checks.

Архитектурное решение

В проекте принят подход Vertical Slice Architecture. Логика бота лежит в src/GmRelay.Bot/Features/*, где каждый сценарий содержит свой handler и локальные DTO.

Причины такого выбора зафиксированы в docs/adr/0001-use-vertical-slice-native-aot-and-aspire.md:

  • сценариев немного, отдельный Clean Architecture слой был бы лишним;
  • зависимости Npgsql и Telegram.Bot стабильны;
  • Native AOT требует явной регистрации зависимостей и аккуратного SQL-маппинга;
  • BackgroundService + PeriodicTimer проще и надёжнее Quartz.NET для периодических проверок расписания и дедлайнов голосования.

Поток Telegram updates

TelegramBotService получает updates через long polling и передаёт их в UpdateRouter.

UpdateRouter явно маршрутизирует:

  • /start, /help, /newsession, /listsessions, /exportcalendar;
  • callback join_session:<sessionId>;
  • callback leave_session:<sessionId>;
  • callback cancel_session:<sessionId>;
  • callback delete_session:<sessionId>;
  • callback reschedule_session:<sessionId>;
  • callback reschedule_vote:<optionId>;
  • callback rsvp:<confirm|decline>:<sessionId>;
  • обычные текстовые сообщения ГМа как ввод 2-3 вариантов времени и дедлайна для активного переноса.

Маршрутизация не использует reflection или assembly scanning.

Планировщик

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

Blazor Server-приложение использует cookie auth и Telegram Login Widget.

Основные сервисы:

  • TelegramAuthService валидирует HMAC-SHA256 подпись Telegram Login Widget и auth_date.
  • 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 операции делегирования проходят через отдельную проверку роли.

Основные страницы:

  • /login — вход через Telegram.
  • / — список групп, где пользователь owner или co-GM.
  • /group/{GroupId} — управляющие группы, будущие сессии и batch bulk-операции.
  • /session/edit/{SessionId} — редактирование названия, времени и ссылки.

Общий домен

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.