Skip to content

Commit

Permalink
Merge pull request #55 from j4asper/54-refactor-to-use-generic-host
Browse files Browse the repository at this point in the history
54 refactor to use generic host
  • Loading branch information
j4asper authored Mar 5, 2025
2 parents f0b57f3 + e4c9e86 commit ff05cb6
Show file tree
Hide file tree
Showing 24 changed files with 331 additions and 254 deletions.
8 changes: 3 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ WORKDIR /KanbanCord
COPY ["KanbanCord.Bot/", "KanbanCord.Bot/"]
COPY ["KanbanCord.Core/", "KanbanCord.Core/"]

ARG application_version=Unknown

RUN dotnet restore "KanbanCord.Bot/KanbanCord.Bot.csproj"
RUN dotnet build "KanbanCord.Bot/KanbanCord.Bot.csproj" -c Release -o /build
RUN dotnet publish "KanbanCord.Bot/KanbanCord.Bot.csproj" -p:PublishSingleFile=true -r linux-musl-x64 --self-contained -c Release -o /publish
RUN dotnet publish "KanbanCord.Bot/KanbanCord.Bot.csproj" -p:PublishSingleFile=true -r linux-musl-x64 --self-contained -c Release -o /publish -p:Version=$application_version


FROM alpine:latest

ARG application_version=Unknown

ENV APPLICATION_VERSION=$application_version

RUN apk upgrade --no-cache && apk add --no-cache icu-libs

WORKDIR /src
Expand Down
37 changes: 37 additions & 0 deletions KanbanCord.Bot/BackgroundServices/BotBackgroundService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Reflection;
using DSharpPlus;
using DSharpPlus.Entities;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace KanbanCord.Bot.BackgroundServices;

public class BotBackgroundService : IHostedService
{
private readonly ILogger<BotBackgroundService> logger;
private readonly IHostApplicationLifetime applicationLifetime;
private readonly DiscordClient discordClient;

public BotBackgroundService(ILogger<BotBackgroundService> logger, IHostApplicationLifetime applicationLifetime, DiscordClient discordClient)
{
this.logger = logger;
this.applicationLifetime = applicationLifetime;
this.discordClient = discordClient;
}

public async Task StartAsync(CancellationToken token)
{
DiscordActivity status = new("out for work", DiscordActivityType.Watching);

await discordClient.ConnectAsync(status, DiscordUserStatus.Online);

logger.LogInformation("Bot User: {username} ({userId})", discordClient.CurrentUser.Username, discordClient.CurrentUser.Id);
logger.LogInformation("Application Version: {version}", Assembly.GetExecutingAssembly().GetName().Version!.ToString());
}

public async Task StopAsync(CancellationToken token)
{
logger.LogInformation("Shutting down bot...");
await discordClient.DisconnectAsync();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using KanbanCord.Core.Models;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;

namespace KanbanCord.Bot.BackgroundServices;

public class DatabaseSetupBackgroundService : IHostedService
{
private readonly IMongoDatabase database;
private readonly ILogger<DatabaseSetupBackgroundService> logger;

public DatabaseSetupBackgroundService(IMongoDatabase database, ILogger<DatabaseSetupBackgroundService> logger)
{
this.database = database;
this.logger = logger;
}

public async Task StartAsync(CancellationToken cancellationToken)
{
var requiredCollections = Enum.GetValues<RequiredCollections>()
.Select(x => x.ToString())
.ToArray();

var cursor = await database.ListCollectionNamesAsync(cancellationToken: cancellationToken);

var collectionList = await cursor.ToListAsync<string>(cancellationToken: cancellationToken);

List<string> createdCollections = [];

foreach (var collectionName in requiredCollections)
{
if (collectionList.Contains(collectionName))
continue;

await database.CreateCollectionAsync(collectionName, cancellationToken: cancellationToken);

createdCollections.Add(collectionName);
}

if (createdCollections.Count != 0)
logger.LogInformation("Created missing mongodb collection(s): {collections}", string.Join(", ", createdCollections));
}

public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
2 changes: 0 additions & 2 deletions KanbanCord.Bot/Commands/BoardCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
using DSharpPlus.Commands;
using DSharpPlus.Commands.Processors.SlashCommands;
using DSharpPlus.Entities;
using KanbanCord.Bot.Extensions;
using KanbanCord.Bot.Helpers;
using KanbanCord.Core.Interfaces;
using KanbanCord.Core.Models;

namespace KanbanCord.Bot.Commands;

Expand Down
12 changes: 9 additions & 3 deletions KanbanCord.Bot/Commands/HelpCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,21 @@
using DSharpPlus.Interactivity;
using KanbanCord.Bot.Extensions;
using KanbanCord.Bot.Helpers;
using KanbanCord.Core.Helpers;
using KanbanCord.Core.Options;
using Microsoft.Extensions.Options;

namespace KanbanCord.Bot.Commands;

public class HelpCommand
{
private const string BaseInviteUrl = "https://discord.com/oauth2/authorize?client_id=";
private readonly string? supportInvite;

public HelpCommand(IOptions<DiscordOptions> options)
{
supportInvite = options.Value.SupportInvite ?? null;
}


[Command("help")]
[Description("Displays all the commands available.")]
Expand Down Expand Up @@ -60,8 +68,6 @@ public async ValueTask ExecuteAsync(SlashCommandContext context)
}

List<DiscordComponent> additionalComponents = [new DiscordLinkButtonComponent(BaseInviteUrl + context.Client.CurrentUser.Id, "Invite")];

var supportInvite = EnvironmentHelpers.GetSupportServerInvite();

if (supportInvite is not null)
additionalComponents.Add(new DiscordLinkButtonComponent(supportInvite, "Support"));
Expand Down
9 changes: 4 additions & 5 deletions KanbanCord.Bot/Commands/StatsCommand.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Reflection;
using DSharpPlus.Commands;
using DSharpPlus.Commands.Processors.SlashCommands;
using DSharpPlus.Entities;
using KanbanCord.Bot.Extensions;
using KanbanCord.Core.Helpers;

namespace KanbanCord.Bot.Commands;

Expand All @@ -14,12 +14,11 @@ public class StatsCommand
[Description("Displays some stats of the bot.")]
public async ValueTask ExecuteAsync(SlashCommandContext context)
{
var botVersion = EnvironmentHelpers.GetApplicationVersion();
var version = Assembly.GetExecutingAssembly().GetName().Version!;

var guildCount = context.Client.Guilds.Count;

GC.Collect(GC.MaxGeneration, GCCollectionMode.Aggressive, true, true);
var heapMemory = $"{GC.GetTotalMemory(true) / 1024 / 1024:n0} MB";
var heapMemory = $"{GC.GetTotalMemory(false) / 1024 / 1024:n0} MB";

using var process = Process.GetCurrentProcess();
var uptime = DateTimeOffset.UtcNow.Subtract(process.StartTime);
Expand All @@ -35,7 +34,7 @@ public async ValueTask ExecuteAsync(SlashCommandContext context)
.AddField("Servers:", guildCount.ToString())
.AddField("Memory Usage:", heapMemory)
.AddField("Uptime:", $"{uptimeDays} days, {remainingHours} hours")
.AddField("Bot Version:", botVersion);
.AddField("Bot Version:", $"v{version.Major}.{version.Minor}.{version.Build}");

await context.RespondAsync(embed);
}
Expand Down
23 changes: 23 additions & 0 deletions KanbanCord.Bot/EventHandlers/GuildCreatedEventHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
using DSharpPlus;
using DSharpPlus.EventArgs;
using Microsoft.Extensions.Logging;

namespace KanbanCord.Bot.EventHandlers;

public class GuildCreatedEventHandler : IEventHandler<GuildCreatedEventArgs>
{
private readonly ILogger<GuildCreatedEventHandler> logger;

public GuildCreatedEventHandler(ILogger<GuildCreatedEventHandler> logger)
{
this.logger = logger;
}


public Task HandleEventAsync(DiscordClient sender, GuildCreatedEventArgs eventArgs)
{
logger.LogInformation("Joined Guild: {guildName} ({guildId})", eventArgs.Guild.Name, eventArgs.Guild.Id);

return Task.CompletedTask;
}
}
7 changes: 6 additions & 1 deletion KanbanCord.Bot/EventHandlers/GuildDeletedEventHandler.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
using DSharpPlus;
using DSharpPlus.EventArgs;
using KanbanCord.Core.Interfaces;
using Microsoft.Extensions.Logging;

namespace KanbanCord.Bot.EventHandlers;

public class GuildDeletedEventHandler : IEventHandler<GuildDeletedEventArgs>
{
private readonly ISettingsRepository _settingsRepository;
private readonly ITaskItemRepository _taskItemRepository;
private readonly ILogger<GuildDeletedEventHandler> logger;

public GuildDeletedEventHandler(ISettingsRepository settingsRepository, ITaskItemRepository taskItemRepository)
public GuildDeletedEventHandler(ISettingsRepository settingsRepository, ITaskItemRepository taskItemRepository, ILogger<GuildDeletedEventHandler> logger)
{
_settingsRepository = settingsRepository;
_taskItemRepository = taskItemRepository;
this.logger = logger;
}


Expand All @@ -24,5 +27,7 @@ public async Task HandleEventAsync(DiscordClient sender, GuildDeletedEventArgs e
await _taskItemRepository.RemoveAllTaskItemsByIdAsync(eventArgs.Guild.Id);

await _settingsRepository.RemoveAsync(eventArgs.Guild.Id);

logger.LogInformation("Left Guild: {guildName} ({guildId})", eventArgs.Guild.Name, eventArgs.Guild.Id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,16 @@ namespace KanbanCord.Bot.EventHandlers;

public class GuildDownloadCompletedEventHandler : IEventHandler<GuildDownloadCompletedEventArgs>
{
private readonly ILogger<GuildDownloadCompletedEventHandler> logger;

public GuildDownloadCompletedEventHandler(ILogger<GuildDownloadCompletedEventHandler> logger)
{
this.logger = logger;
}

public Task HandleEventAsync(DiscordClient sender, GuildDownloadCompletedEventArgs eventArgs)
{
sender.Logger.LogInformation($"Guild Download Completed: Downloaded {eventArgs.Guilds.Count} guilds.");
logger.LogInformation("Guild Download Completed: Downloaded {count} guilds.", eventArgs.Guilds.Count);

return Task.CompletedTask;
}
Expand Down
46 changes: 46 additions & 0 deletions KanbanCord.Bot/Extensions/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Serilog;

namespace KanbanCord.Bot.Extensions;

public static class HostBuilderExtensions
{
public static IHostBuilder AddHostDependencies(this IHostBuilder hostBuilder)
{
hostBuilder.ConfigureServices((hostContext, services) =>
{
services.AddServices(hostContext.Configuration);
services.AddDiscordConfiguration(hostContext.Configuration);
});

return hostBuilder;
}

public static IHostBuilder UseSerilog(this IHostBuilder hostBuilder)
{
hostBuilder.ConfigureLogging((hostContext, logging) =>
{
logging.ClearProviders();

Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(hostContext.Configuration)
.CreateLogger();

logging.AddSerilog();
});

return hostBuilder;
}

public static IHostBuilder UseDefaultServiceProvider(this IHostBuilder hostBuilder)
{
hostBuilder.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true;
options.ValidateOnBuild = true;
});

return hostBuilder;
}
}
46 changes: 22 additions & 24 deletions KanbanCord.Bot/Extensions/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,49 @@
using DSharpPlus.Commands.Processors.SlashCommands;
using DSharpPlus.Extensions;
using DSharpPlus.Interactivity.Extensions;
using KanbanCord.Bot.BackgroundServices;
using KanbanCord.Bot.EventHandlers;
using KanbanCord.Core.Helpers;
using KanbanCord.Core.Interfaces;
using KanbanCord.Core.Options;
using KanbanCord.Core.Repositories;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;

namespace KanbanCord.Bot.Extensions;

public static class ServiceCollectionExtensions
{
public static ServiceCollection AddServices(this ServiceCollection services)
public static IServiceCollection AddServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddLogging(x =>
{
x.SetMinimumLevel(LogLevel.Information);
x.AddSimpleConsole(options =>
{
options.UseUtcTimestamp = true;
options.TimestampFormat = "[MMM dd yyyy - HH:mm:ss UTC] ";
});
});
services.AddOptionsWithValidateOnStart<DatabaseOptions>()
.BindConfiguration(DatabaseOptions.Database)
.ValidateDataAnnotations();

services.AddOptionsWithValidateOnStart<DiscordOptions>()
.BindConfiguration(DiscordOptions.Discord)
.ValidateDataAnnotations();

services.AddHostedService<BotBackgroundService>();
services.AddHostedService<DatabaseSetupBackgroundService>();

services.AddScoped<ITaskItemRepository, TaskItemRepository>();
services.AddScoped<ISettingsRepository, SettingsRepository>();
services.AddSingleton<ITaskItemRepository, TaskItemRepository>();
services.AddSingleton<ISettingsRepository, SettingsRepository>();

return services;
}

public static ServiceCollection AddDatabase(this ServiceCollection services)
{
services.AddScoped<IMongoDatabase>(_ =>
new MongoClient(EnvironmentHelpers.GetDatabaseConnectionString())
.GetDatabase(EnvironmentHelpers.GetDatabaseName())
services.AddSingleton<IMongoDatabase>(_ =>
new MongoClient(configuration.GetRequiredSection("Database:ConnectionString").Value)
.GetDatabase(configuration.GetRequiredSection("Database:Name").Value)
);

return services;
}

public static ServiceCollection AddDiscordConfiguration(this ServiceCollection services)
public static IServiceCollection AddDiscordConfiguration(this IServiceCollection services, IConfiguration configuration)
{
const DiscordIntents intents = DiscordIntents.None
| DiscordIntents.Guilds;

services.AddDiscordClient(EnvironmentHelpers.GetBotToken(), intents)
services.AddDiscordClient(configuration.GetRequiredSection("Discord:Token").Value!, intents)
.Configure<DiscordConfiguration>(discordConfiguration =>
{
discordConfiguration.LogUnknownAuditlogs = false;
Expand All @@ -70,6 +67,7 @@ public static ServiceCollection AddDiscordConfiguration(this ServiceCollection s
.ConfigureEventHandlers(eventHandlingBuilder =>
{
eventHandlingBuilder.AddEventHandlers<GuildDeletedEventHandler>();
eventHandlingBuilder.AddEventHandlers<GuildCreatedEventHandler>();
eventHandlingBuilder.AddEventHandlers<ComponentInteractionCreatedEventHandler>();
eventHandlingBuilder.AddEventHandlers<GuildDownloadCompletedEventHandler>();
});
Expand Down
Loading

0 comments on commit ff05cb6

Please sign in to comment.