feat: add Discord NetCord gateway worker
PR Checks / test-and-build (pull_request) Successful in 5m46s

Add a separate GmRelay.DiscordBot worker using NetCord Gateway with startup token validation, PostgreSQL datasource registration, slash-command setup, component interaction service registration, and lifecycle logging.

Wire the Discord service through Aspire AppHost, Docker Compose, PR checks, deploy image build/push/scan/pull steps, README docs, and synchronized version 2.2.0.

Add TDD coverage for project isolation, token validation, startup wiring, runtime wiring, and version synchronization.

Bump version -> 2.2.0
This commit is contained in:
2026-05-18 16:04:31 +03:00
parent ab59d234f3
commit 05ca8061e9
22 changed files with 1864 additions and 15 deletions
@@ -0,0 +1,43 @@
using NetCord.Gateway;
using NetCord.Hosting.Gateway;
namespace GmRelay.DiscordBot.Infrastructure.Logging;
public sealed class DiscordGatewayLifecycleLogger(
ILogger<DiscordGatewayLifecycleLogger> logger)
: IConnectGatewayHandler,
IReadyGatewayHandler,
IDisconnectGatewayHandler,
IResumeGatewayHandler
{
public ValueTask HandleAsync()
{
logger.LogInformation("Discord gateway connected");
return ValueTask.CompletedTask;
}
public ValueTask HandleAsync(ReadyEventArgs arg)
{
logger.LogInformation(
"Discord gateway ready for application {ApplicationId} in {GuildCount} guilds",
arg.ApplicationId,
arg.GuildIds.Count);
return ValueTask.CompletedTask;
}
public ValueTask HandleAsync(DisconnectEventArgs arg)
{
logger.LogWarning(
"Discord gateway disconnected; reconnect scheduled: {Reconnect}",
arg.Reconnect);
return ValueTask.CompletedTask;
}
ValueTask IResumeGatewayHandler.HandleAsync()
{
logger.LogInformation("Discord gateway session resumed");
return ValueTask.CompletedTask;
}
}
@@ -0,0 +1,14 @@
using System.Text.RegularExpressions;
namespace GmRelay.DiscordBot.Infrastructure.Logging;
internal static partial class SecretRedactor
{
public static string RedactConnectionString(string connectionString)
{
return PasswordPattern().Replace(connectionString, "$1***");
}
[GeneratedRegex(@"(?i)(Password\s*=\s*)[^;]+")]
private static partial Regex PasswordPattern();
}