8f0f2ef7e7
Moves the game-creation wizard state machine, view builder, and platform-neutral contracts (callback data, step names, storage exception, club option, step limits) from GmRelay.Bot to GmRelay.Shared. Telegram continues to work through a new TelegramWizardMessenger implementing IWizardMessenger and a WizardInteractionMapper that converts Update → WizardInteraction. Wires the new platform column on wizard_drafts (V032 migration) and switches chat/owner/thread/message ids to TEXT so the same table can hold Discord snowflakes later. - GameCreationWizard: now in Shared, takes IWizardMessenger + IWizardDraftRepository, dispatches on WizardInteraction. - New IWizardMessenger contract with Edit/Send/Answer/GetOwnerClubs (returns string ids so Telegram longs and Discord snowflakes both fit). - New WizardStepViewBuilder in Shared returns (text, IReadOnlyList<WizardAction>); TelegramWizardMessenger renders actions into InlineKeyboardMarkup via a new Bot-side ToInlineKeyboard helper. - New WizardInteractionMapper in Bot (5-case test) converts Telegram Update to WizardInteraction. - WizardDraft gains a Platform column; ChatId/MessageThreadId/OwnerId/ DraftMessageId switched to string. V032 migrates existing rows and rebuilds the owner lookup index on (platform, owner_id). - All existing wizard / create-session tests updated to the new contract (HandleInteractionAsync + WizardInteraction). Wizard callback-data format preserved. - dotnet build clean, dotnet format --verify-no-changes clean, all 101 wizard tests pass.
123 lines
7.3 KiB
C#
123 lines
7.3 KiB
C#
using GmRelay.Bot.Features.Notifications;
|
|
using GmRelay.Bot.Features.Sessions.CreateSession;
|
|
using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
|
|
using GmRelay.Bot.Features.Sessions.RescheduleSession;
|
|
using GmRelay.Bot.Infrastructure.Database;
|
|
using GmRelay.Shared.Features.Sessions.RescheduleSession;
|
|
using GmRelay.Bot.Infrastructure.Health;
|
|
using GmRelay.Bot.Infrastructure.Logging;
|
|
using GmRelay.Bot.Infrastructure.Telegram;
|
|
using GmRelay.Shared.Features.Confirmation.HandleRsvp;
|
|
using GmRelay.Shared.Features.Confirmation.SendConfirmation;
|
|
using GmRelay.Shared.Features.Notifications;
|
|
using GmRelay.Shared.Features.Reminders.SendJoinLink;
|
|
using GmRelay.Shared.Features.Reminders.SendOneHourReminder;
|
|
using GmRelay.Shared.Features.Sessions.CreateSession;
|
|
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
|
|
using GmRelay.Shared.Infrastructure.Scheduling;
|
|
using GmRelay.Shared.Platform;
|
|
using Npgsql;
|
|
using Telegram.Bot;
|
|
|
|
[module: Dapper.DapperAot]
|
|
|
|
var builder = Host.CreateApplicationBuilder(args);
|
|
|
|
// ── Aspire service defaults (OpenTelemetry, health checks) ───────────
|
|
builder.AddServiceDefaults();
|
|
|
|
// ── PostgreSQL (ручная регистрация — AOT safe, без Aspire-магии) ─────
|
|
builder.Services.AddSingleton<NpgsqlDataSource>(sp =>
|
|
{
|
|
var config = sp.GetRequiredService<IConfiguration>();
|
|
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
|
|
var connectionString = config.GetConnectionString("gmrelaydb")
|
|
?? throw new InvalidOperationException(
|
|
"ConnectionStrings:gmrelaydb is required. Set via environment variable ConnectionStrings__gmrelaydb.");
|
|
|
|
var logger = loggerFactory.CreateLogger("GmRelay.Bot.Startup");
|
|
logger.LogInformation(
|
|
"Configured PostgreSQL data source with connection string {ConnectionString}",
|
|
SecretRedactor.RedactConnectionString(connectionString));
|
|
|
|
return NpgsqlDataSource.Create(connectionString);
|
|
});
|
|
|
|
// ── Database migrations ──────────────────────────────────────────────
|
|
builder.Services.AddSingleton<DbMigrator>();
|
|
|
|
// ── Telegram Bot Client ──────────────────────────────────────────────
|
|
builder.Services.AddSingleton<ITelegramBotClient>(sp =>
|
|
{
|
|
var config = sp.GetRequiredService<IConfiguration>();
|
|
var token = config["Telegram:BotToken"]
|
|
?? throw new InvalidOperationException(
|
|
"Telegram:BotToken is required. Set via environment variable Telegram__BotToken or appsettings.json.");
|
|
return new TelegramBotClient(token);
|
|
});
|
|
builder.Services.AddSingleton<ITelegramUpdateSource, TelegramUpdateSource>();
|
|
builder.Services.AddSingleton<IPlatformMessenger, TelegramPlatformMessenger>();
|
|
builder.Services.AddSingleton(new PlatformSchedulerOptions(PlatformKind.Telegram));
|
|
|
|
// ── Feature handlers (explicit registration — AOT safe) ──────────────
|
|
builder.Services.AddSingleton<SendConfirmationHandler>();
|
|
builder.Services.AddSingleton<ISendConfirmationHandler>(sp => sp.GetRequiredService<SendConfirmationHandler>());
|
|
builder.Services.AddSingleton<PlatformDirectNotificationSender>();
|
|
builder.Services.AddSingleton<HandleRsvpHandler>();
|
|
builder.Services.AddSingleton<SendJoinLinkHandler>();
|
|
builder.Services.AddSingleton<ISendJoinLinkHandler>(sp => sp.GetRequiredService<SendJoinLinkHandler>());
|
|
builder.Services.AddSingleton<SendOneHourReminderHandler>();
|
|
builder.Services.AddSingleton<ISendOneHourReminderHandler>(sp => sp.GetRequiredService<SendOneHourReminderHandler>());
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.CreateSession.CreateSessionHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Bot.Features.Sessions.CreateSession.CreateSessionHandler>();
|
|
|
|
// Wizard services (issue #111)
|
|
builder.Services.AddSingleton<IWizardDraftRepository, WizardDraftRepository>();
|
|
builder.Services.AddSingleton<IWizardMessenger, TelegramWizardMessenger>();
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.CreateSession.Wizard.GameCreationWizard>();
|
|
builder.Services.AddSingleton<IScheduleMessageUpdateLock, ScheduleMessageUpdateLock>();
|
|
builder.Services.AddSingleton<JoinSessionHandler>();
|
|
builder.Services.AddSingleton<LeaveSessionHandler>();
|
|
builder.Services.AddSingleton<PromoteWaitlistedPlayerHandler>();
|
|
builder.Services.AddSingleton<CancelSessionHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.ListSessions.DeleteSessionHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Bot.Features.Sessions.ListSessions.DeleteSessionHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.ListSessions.ListSessionsHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Bot.Features.Sessions.ListSessions.ListSessionsHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.ExportCalendar.ExportCalendarHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Bot.Features.Sessions.ExportCalendar.ExportCalendarHandler>();
|
|
builder.Services.AddSingleton<InitiateRescheduleHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.RescheduleSession.HandleRescheduleTimeInputHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Bot.Features.Sessions.RescheduleSession.HandleRescheduleTimeInputHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Shared.Features.Sessions.RescheduleSession.HandleRescheduleVoteHandler>();
|
|
builder.Services.AddSingleton<GmRelay.Bot.Features.Sessions.RescheduleSession.HandleRescheduleVoteHandler>();
|
|
builder.Services.AddSingleton<RescheduleVotingFinalizer>();
|
|
|
|
builder.Services.AddSingleton<DirectSessionNotificationSender>();
|
|
|
|
// ── Telegram infrastructure ──────────────────────────────────────────
|
|
builder.Services.AddSingleton<UpdateRouter>();
|
|
builder.Services.AddSingleton<ITelegramUpdateHandler>(sp => sp.GetRequiredService<UpdateRouter>());
|
|
builder.Services.AddHostedService<TelegramMiniAppMenuButtonService>();
|
|
builder.Services.AddHostedService<TelegramBotService>();
|
|
|
|
// ── Clock and scheduling ──────────────────────────────────────────────
|
|
builder.Services.AddSingleton<ISystemClock, GmRelay.Bot.Infrastructure.Scheduling.SystemClock>();
|
|
builder.Services.AddSingleton<ISessionTriggerStore, DbSessionTriggerStore>();
|
|
|
|
// ── Session scheduler ────────────────────────────────────────────────
|
|
builder.Services.AddHostedService<SessionSchedulerService>();
|
|
builder.Services.AddHostedService<RescheduleVotingDeadlineService>();
|
|
builder.Services.AddHostedService<WizardDraftCleanupService>();
|
|
|
|
// ── Health check server ──────────────────────────────────────────────
|
|
builder.Services.AddHostedService<BotHealthCheckHostedService>();
|
|
|
|
var host = builder.Build();
|
|
|
|
// ── Run database migrations on startup ───────────────────────────────
|
|
var migrator = host.Services.GetRequiredService<DbMigrator>();
|
|
migrator.MigrateUp();
|
|
|
|
await host.RunAsync();
|