From a9853021f4c03edf9b0d297ed976332adb2b175c Mon Sep 17 00:00:00 2001 From: Noi Date: Mon, 21 Nov 2022 10:45:46 -0800 Subject: [PATCH 1/5] Edit SQL pool config, usage --- Configuration.cs | 2 +- Data/BotDatabaseContext.cs | 7 ++++++- ShardManager.cs | 5 ++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Configuration.cs b/Configuration.cs index 48503a7..aff4457 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -73,7 +73,7 @@ class Configuration { SqlDatabase = ReadConfKey(jc, nameof(SqlDatabase), false); SqlUsername = ReadConfKey(jc, nameof(SqlUsername), true); SqlPassword = ReadConfKey(jc, nameof(SqlPassword), true); - SqlApplicationName = $"ClientShard{ShardStart}+{ShardAmount}"; + SqlApplicationName = $"Shard{ShardStart:00}-{ShardStart + ShardAmount - 1:00}"; } private static T? ReadConfKey(JObject jc, string key, [DoesNotReturnIf(true)] bool failOnEmpty) { diff --git a/Data/BotDatabaseContext.cs b/Data/BotDatabaseContext.cs index 2fc2616..18e11a9 100644 --- a/Data/BotDatabaseContext.cs +++ b/Data/BotDatabaseContext.cs @@ -15,7 +15,12 @@ public class BotDatabaseContext : DbContext { Username = conf.SqlUsername, Password = conf.SqlPassword, ApplicationName = conf.SqlApplicationName, - MaxPoolSize = Math.Max((int)Math.Ceiling(conf.ShardAmount * 2 * 0.6), 8) + + // Let's see if this works? + ConnectionIdleLifetime = 60, + MaxPoolSize = Math.Max( + (int)Math.Ceiling(conf.ShardAmount * 2 * 0.75), + (int)Math.Ceiling(ShardManager.MaxConcurrentOperations * 2.5)) }.ToString(); } diff --git a/ShardManager.cs b/ShardManager.cs index dda1d6b..cd09a00 100644 --- a/ShardManager.cs +++ b/ShardManager.cs @@ -1,6 +1,5 @@ global using Discord; global using Discord.WebSocket; -using BirthdayBot.BackgroundServices; using Discord.Interactions; using Microsoft.Extensions.DependencyInjection; using System.Text; @@ -23,9 +22,9 @@ class ShardManager : IDisposable { /// /// Number of concurrent shard startups to happen on each check. - /// This value is also used in . + /// This value also determines the maximum amount of concurrent background database operations. /// - public const int MaxConcurrentOperations = 4; + public const int MaxConcurrentOperations = 3; /// /// Amount of time without a completed background service run before a shard instance From e62a89d0a0e44186cd8b653b26f254f41416835f Mon Sep 17 00:00:00 2001 From: Noi Date: Mon, 21 Nov 2022 19:17:07 -0800 Subject: [PATCH 2/5] Use background service semaphore Must have missed it the first time... --- BackgroundServices/AutoUserDownload.cs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/BackgroundServices/AutoUserDownload.cs b/BackgroundServices/AutoUserDownload.cs index c333c11..229feb0 100644 --- a/BackgroundServices/AutoUserDownload.cs +++ b/BackgroundServices/AutoUserDownload.cs @@ -1,7 +1,7 @@ using BirthdayBot.Data; +using Microsoft.EntityFrameworkCore; namespace BirthdayBot.BackgroundServices; - /// /// Proactively fills the user cache for guilds in which any birthday data already exists. /// @@ -9,14 +9,23 @@ class AutoUserDownload : BackgroundService { public AutoUserDownload(ShardInstance instance) : base(instance) { } public override async Task OnTick(int tickCount, CancellationToken token) { - using var db = new BotDatabaseContext(); - // Take action if a guild's cache is incomplete... var incompleteCaches = ShardInstance.DiscordClient.Guilds.Where(g => !g.HasAllMembers).Select(g => (long)g.Id).ToHashSet(); // ...and if the guild contains any user data - var mustFetch = db.UserEntries.Where(e => incompleteCaches.Contains(e.GuildId)).Select(e => e.GuildId).Distinct(); - - int processed = 0; + IEnumerable mustFetch; + try { + await DbConcurrentOperationsLock.WaitAsync(token); + using var db = new BotDatabaseContext(); + mustFetch = db.UserEntries.AsNoTracking() + .Where(e => incompleteCaches.Contains(e.GuildId)).Select(e => e.GuildId).Distinct() + .ToList(); + } finally { + try { + DbConcurrentOperationsLock.Release(); + } catch (ObjectDisposedException) { } + } + + var processed = 0; foreach (var item in mustFetch) { // May cause a disconnect in certain situations. Cancel all further attempts until the next pass if it happens. if (ShardInstance.DiscordClient.ConnectionState != ConnectionState.Connected) break; @@ -28,6 +37,6 @@ class AutoUserDownload : BackgroundService { processed++; } - if (processed > 100) Log($"Explicit user list request processed for {processed} guild(s)."); + if (processed > 25) Log($"Explicit user list request processed for {processed} guild(s)."); } } From fbd730581896f7be7e466033fae449374d990985 Mon Sep 17 00:00:00 2001 From: Noi Date: Mon, 21 Nov 2022 20:38:06 -0800 Subject: [PATCH 3/5] Revert, unset pool configuration changes --- Data/BotDatabaseContext.cs | 8 +------- ShardManager.cs | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Data/BotDatabaseContext.cs b/Data/BotDatabaseContext.cs index 18e11a9..99c9c07 100644 --- a/Data/BotDatabaseContext.cs +++ b/Data/BotDatabaseContext.cs @@ -14,13 +14,7 @@ public class BotDatabaseContext : DbContext { Database = conf.SqlDatabase, Username = conf.SqlUsername, Password = conf.SqlPassword, - ApplicationName = conf.SqlApplicationName, - - // Let's see if this works? - ConnectionIdleLifetime = 60, - MaxPoolSize = Math.Max( - (int)Math.Ceiling(conf.ShardAmount * 2 * 0.75), - (int)Math.Ceiling(ShardManager.MaxConcurrentOperations * 2.5)) + ApplicationName = conf.SqlApplicationName }.ToString(); } diff --git a/ShardManager.cs b/ShardManager.cs index cd09a00..cbaab4f 100644 --- a/ShardManager.cs +++ b/ShardManager.cs @@ -24,7 +24,7 @@ class ShardManager : IDisposable { /// Number of concurrent shard startups to happen on each check. /// This value also determines the maximum amount of concurrent background database operations. /// - public const int MaxConcurrentOperations = 3; + public const int MaxConcurrentOperations = 4; /// /// Amount of time without a completed background service run before a shard instance From 2c920e9794f8bc7d2bdd0b887e70567fd67592b2 Mon Sep 17 00:00:00 2001 From: Noi Date: Mon, 21 Nov 2022 21:20:48 -0800 Subject: [PATCH 4/5] Upgrade EFCore, switch to bulk operations --- BackgroundServices/DataRetention.cs | 43 ++++++++++++++--------------- BirthdayBot.csproj | 12 ++++---- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/BackgroundServices/DataRetention.cs b/BackgroundServices/DataRetention.cs index 2caa3e8..ee5f942 100644 --- a/BackgroundServices/DataRetention.cs +++ b/BackgroundServices/DataRetention.cs @@ -1,4 +1,5 @@ using BirthdayBot.Data; +using Microsoft.EntityFrameworkCore; using System.Text; namespace BirthdayBot.BackgroundServices; @@ -31,34 +32,32 @@ class DataRetention : BackgroundService { private async Task RemoveStaleEntriesAsync() { using var db = new BotDatabaseContext(); var now = DateTimeOffset.UtcNow; - int updatedGuilds = 0, updatedUsers = 0; + // Update guilds + var localGuilds = ShardInstance.DiscordClient.Guilds.Select(g => (long)g.Id).ToList(); + var updatedGuilds = await db.GuildConfigurations + .Where(g => localGuilds.Contains(g.GuildId)) + .ExecuteUpdateAsync(upd => upd.SetProperty(p => p.LastSeen, now)); + + // Update guild users + var updatedUsers = 0; foreach (var guild in ShardInstance.DiscordClient.Guilds) { - // Update guild, fetch users from database - var dbGuild = db.GuildConfigurations.Where(s => s.GuildId == (long)guild.Id).FirstOrDefault(); - if (dbGuild == null) continue; - dbGuild.LastSeen = now; - updatedGuilds++; - - // Update users - var localIds = guild.Users.Select(u => (long)u.Id); - var dbSavedIds = db.UserEntries.Where(e => e.GuildId == (long)guild.Id).Select(e => e.UserId); - var usersToUpdate = localIds.Intersect(dbSavedIds).ToHashSet(); - foreach (var user in db.UserEntries.Where(e => e.GuildId == (long)guild.Id && usersToUpdate.Contains(e.UserId))) { - user.LastSeen = now; - updatedUsers++; - } + var localUsers = guild.Users.Select(u => (long)u.Id).ToList(); + updatedUsers += await db.UserEntries + .Where(gu => gu.GuildId == (long)guild.Id) + .Where(gu => localUsers.Contains(gu.UserId)) + .ExecuteUpdateAsync(upd => upd.SetProperty(p => p.LastSeen, now)); } // And let go of old data - var staleGuilds = db.GuildConfigurations.Where(s => now - TimeSpan.FromDays(StaleGuildThreshold) > s.LastSeen); - var staleUsers = db.UserEntries.Where(e => now - TimeSpan.FromDays(StaleUserThreashold) > e.LastSeen); - int staleGuildCount = staleGuilds.Count(), staleUserCount = staleUsers.Count(); - db.GuildConfigurations.RemoveRange(staleGuilds); - db.UserEntries.RemoveRange(staleUsers); - - await db.SaveChangesAsync(CancellationToken.None); + var staleGuildCount = await db.GuildConfigurations + .Where(g => now - TimeSpan.FromDays(StaleGuildThreshold) > g.LastSeen) + .ExecuteDeleteAsync(); + var staleUserCount = await db.UserEntries + .Where(gu => now - TimeSpan.FromDays(StaleUserThreashold) > gu.LastSeen) + .ExecuteDeleteAsync(); + // Build report var resultText = new StringBuilder(); resultText.Append($"Updated {updatedGuilds} guilds, {updatedUsers} users."); if (staleGuildCount != 0 || staleUserCount != 0) { diff --git a/BirthdayBot.csproj b/BirthdayBot.csproj index 5192d10..d8150f9 100644 --- a/BirthdayBot.csproj +++ b/BirthdayBot.csproj @@ -5,7 +5,7 @@ net6.0 enable enable - 3.4.5 + 3.4.6 NoiTheCat @@ -23,16 +23,16 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + From a3ac5d6eded52778bc0fd7b8bca8166eae5c0135 Mon Sep 17 00:00:00 2001 From: Noi Date: Tue, 22 Nov 2022 23:19:37 -0800 Subject: [PATCH 5/5] Change data types from long to ulong --- ApplicationCommands/BirthdayModule.cs | 4 +- ApplicationCommands/ConfigModule.cs | 10 +- .../Preconditions/EnforceBlocking.cs | 6 +- .../Preconditions/RequireBotModerator.cs | 2 +- BackgroundServices/AutoUserDownload.cs | 4 +- BackgroundServices/BirthdayRoleUpdate.cs | 4 +- BackgroundServices/DataRetention.cs | 6 +- Data/BlocklistEntry.cs | 6 +- Data/Extensions.cs | 8 +- Data/GuildConfig.cs | 24 +-- .../20221123062847_LongToUlong.Designer.cs | 161 ++++++++++++++++ Data/Migrations/20221123062847_LongToUlong.cs | 177 ++++++++++++++++++ .../BotDatabaseContextModelSnapshot.cs | 46 ++--- Data/UserEntry.cs | 14 +- 14 files changed, 404 insertions(+), 68 deletions(-) create mode 100644 Data/Migrations/20221123062847_LongToUlong.Designer.cs create mode 100644 Data/Migrations/20221123062847_LongToUlong.cs diff --git a/ApplicationCommands/BirthdayModule.cs b/ApplicationCommands/BirthdayModule.cs index d4933d0..4d4991a 100644 --- a/ApplicationCommands/BirthdayModule.cs +++ b/ApplicationCommands/BirthdayModule.cs @@ -222,10 +222,10 @@ public class BirthdayModule : BotModuleBase { private static List GetSortedUserList(SocketGuild guild) { using var db = new BotDatabaseContext(); var query = from row in db.UserEntries - where row.GuildId == (long)guild.Id + where row.GuildId == guild.Id orderby row.BirthMonth, row.BirthDay select new { - UserId = (ulong)row.UserId, + row.UserId, Month = row.BirthMonth, Day = row.BirthDay, Zone = row.TimeZone diff --git a/ApplicationCommands/ConfigModule.cs b/ApplicationCommands/ConfigModule.cs index d9036fa..10fed49 100644 --- a/ApplicationCommands/ConfigModule.cs +++ b/ApplicationCommands/ConfigModule.cs @@ -56,7 +56,7 @@ public class ConfigModule : BotModuleBase { [SlashCommand("set-channel", HelpPfxModOnly + HelpSubCmdChannel + HelpPofxBlankUnset)] public async Task CmdSetChannel([Summary(description: HelpOptChannel)] SocketTextChannel? channel = null) { - await DoDatabaseUpdate(Context, s => s.AnnouncementChannel = (long?)channel?.Id); + await DoDatabaseUpdate(Context, s => s.AnnouncementChannel = channel?.Id); await RespondAsync(":white_check_mark: The announcement channel has been " + (channel == null ? "unset." : $"set to **{channel.Name}**.")); } @@ -126,7 +126,7 @@ public class ConfigModule : BotModuleBase { await RespondAsync(":x: This role cannot be used for this setting.", ephemeral: true); return; } - await DoDatabaseUpdate(Context, s => s.BirthdayRole = (long)role.Id); + await DoDatabaseUpdate(Context, s => s.BirthdayRole = role.Id); await RespondAsync($":white_check_mark: The birthday role has been set to **{role.Name}**.").ConfigureAwait(false); } @@ -136,7 +136,7 @@ public class ConfigModule : BotModuleBase { await RespondAsync(":x: This role cannot be used for this setting.", ephemeral: true); return; } - await DoDatabaseUpdate(Context, s => s.ModeratorRole = (long?)role?.Id); + await DoDatabaseUpdate(Context, s => s.ModeratorRole = role?.Id); await RespondAsync(":white_check_mark: The moderator role has been " + (role == null ? "unset." : $"set to **{role.Name}**.")); } @@ -154,7 +154,7 @@ public class ConfigModule : BotModuleBase { // setting: true to add (set), false to remove (unset) using var db = new BotDatabaseContext(); var existing = db.BlocklistEntries - .Where(bl => bl.GuildId == (long)user.Guild.Id && bl.UserId == (long)user.Id).FirstOrDefault(); + .Where(bl => bl.GuildId == user.Guild.Id && bl.UserId == user.Id).FirstOrDefault(); bool already = (existing != null) == setting; if (already) { @@ -162,7 +162,7 @@ public class ConfigModule : BotModuleBase { return; } - if (setting) db.BlocklistEntries.Add(new BlocklistEntry() { GuildId = (long)user.Guild.Id, UserId = (long)user.Id }); + if (setting) db.BlocklistEntries.Add(new BlocklistEntry() { GuildId = user.Guild.Id, UserId = user.Id }); else db.Remove(existing!); await db.SaveChangesAsync(); diff --git a/ApplicationCommands/Preconditions/EnforceBlocking.cs b/ApplicationCommands/Preconditions/EnforceBlocking.cs index 9ed68a5..2bf692e 100644 --- a/ApplicationCommands/Preconditions/EnforceBlocking.cs +++ b/ApplicationCommands/Preconditions/EnforceBlocking.cs @@ -24,8 +24,8 @@ class EnforceBlockingAttribute : PreconditionAttribute { using var db = new BotDatabaseContext(); var settings = (from row in db.GuildConfigurations - where row.GuildId == (long)guild.Id - select new { ModRole = (ulong?)row.ModeratorRole, ModMode = row.Moderated }).FirstOrDefault(); + where row.GuildId == guild.Id + select new { ModRole = row.ModeratorRole, ModMode = row.Moderated }).FirstOrDefault(); if (settings != null) { // Bot moderators override all blocking measures in place if (user.Roles.Any(r => r.Id == settings.ModRole)) return Task.FromResult(PreconditionResult.FromSuccess()); @@ -34,7 +34,7 @@ class EnforceBlockingAttribute : PreconditionAttribute { if (settings.ModMode) return Task.FromResult(PreconditionResult.FromError(FailModerated)); // Check if user exists in blocklist - if (db.BlocklistEntries.Where(row => row.GuildId == (long)guild.Id && row.UserId == (long)user.Id).Any()) + if (db.BlocklistEntries.Where(row => row.GuildId == guild.Id && row.UserId == user.Id).Any()) return Task.FromResult(PreconditionResult.FromError(FailBlocked)); } diff --git a/ApplicationCommands/Preconditions/RequireBotModerator.cs b/ApplicationCommands/Preconditions/RequireBotModerator.cs index f62aa07..74aaf3b 100644 --- a/ApplicationCommands/Preconditions/RequireBotModerator.cs +++ b/ApplicationCommands/Preconditions/RequireBotModerator.cs @@ -22,7 +22,7 @@ class RequireBotModeratorAttribute : PreconditionAttribute { if (user.GuildPermissions.ManageGuild) return Task.FromResult(PreconditionResult.FromSuccess()); using var db = new BotDatabaseContext(); var checkRole = (ulong?)db.GuildConfigurations - .Where(g => g.GuildId == (long)((SocketGuild)context.Guild).Id) + .Where(g => g.GuildId == ((SocketGuild)context.Guild).Id) .Select(g => g.ModeratorRole).FirstOrDefault(); if (checkRole.HasValue && user.Roles.Any(r => r.Id == checkRole.Value)) return Task.FromResult(PreconditionResult.FromSuccess()); diff --git a/BackgroundServices/AutoUserDownload.cs b/BackgroundServices/AutoUserDownload.cs index 229feb0..dffcd6f 100644 --- a/BackgroundServices/AutoUserDownload.cs +++ b/BackgroundServices/AutoUserDownload.cs @@ -10,9 +10,9 @@ class AutoUserDownload : BackgroundService { public override async Task OnTick(int tickCount, CancellationToken token) { // Take action if a guild's cache is incomplete... - var incompleteCaches = ShardInstance.DiscordClient.Guilds.Where(g => !g.HasAllMembers).Select(g => (long)g.Id).ToHashSet(); + var incompleteCaches = ShardInstance.DiscordClient.Guilds.Where(g => !g.HasAllMembers).Select(g => g.Id).ToHashSet(); // ...and if the guild contains any user data - IEnumerable mustFetch; + IEnumerable mustFetch; try { await DbConcurrentOperationsLock.WaitAsync(token); using var db = new BotDatabaseContext(); diff --git a/BackgroundServices/BirthdayRoleUpdate.cs b/BackgroundServices/BirthdayRoleUpdate.cs index 48d908b..5a3d519 100644 --- a/BackgroundServices/BirthdayRoleUpdate.cs +++ b/BackgroundServices/BirthdayRoleUpdate.cs @@ -27,9 +27,9 @@ class BirthdayRoleUpdate : BackgroundService { private async Task ProcessBirthdaysAsync(CancellationToken token) { // For database efficiency, fetch all database information at once before proceeding using var db = new BotDatabaseContext(); - var shardGuilds = ShardInstance.DiscordClient.Guilds.Select(g => (long)g.Id).ToHashSet(); + var shardGuilds = ShardInstance.DiscordClient.Guilds.Select(g => g.Id).ToHashSet(); var presentGuildSettings = db.GuildConfigurations.Where(s => shardGuilds.Contains(s.GuildId)); - var guildChecks = presentGuildSettings.ToList().Select(s => Tuple.Create((ulong)s.GuildId, s)); + var guildChecks = presentGuildSettings.ToList().Select(s => Tuple.Create(s.GuildId, s)); var exceptions = new List(); foreach (var (guildId, settings) in guildChecks) { diff --git a/BackgroundServices/DataRetention.cs b/BackgroundServices/DataRetention.cs index ee5f942..6f0a0e6 100644 --- a/BackgroundServices/DataRetention.cs +++ b/BackgroundServices/DataRetention.cs @@ -34,7 +34,7 @@ class DataRetention : BackgroundService { var now = DateTimeOffset.UtcNow; // Update guilds - var localGuilds = ShardInstance.DiscordClient.Guilds.Select(g => (long)g.Id).ToList(); + var localGuilds = ShardInstance.DiscordClient.Guilds.Select(g => g.Id).ToList(); var updatedGuilds = await db.GuildConfigurations .Where(g => localGuilds.Contains(g.GuildId)) .ExecuteUpdateAsync(upd => upd.SetProperty(p => p.LastSeen, now)); @@ -42,9 +42,9 @@ class DataRetention : BackgroundService { // Update guild users var updatedUsers = 0; foreach (var guild in ShardInstance.DiscordClient.Guilds) { - var localUsers = guild.Users.Select(u => (long)u.Id).ToList(); + var localUsers = guild.Users.Select(u => u.Id).ToList(); updatedUsers += await db.UserEntries - .Where(gu => gu.GuildId == (long)guild.Id) + .Where(gu => gu.GuildId == guild.Id) .Where(gu => localUsers.Contains(gu.UserId)) .ExecuteUpdateAsync(upd => upd.SetProperty(p => p.LastSeen, now)); } diff --git a/Data/BlocklistEntry.cs b/Data/BlocklistEntry.cs index 34829cc..b9b30e4 100644 --- a/Data/BlocklistEntry.cs +++ b/Data/BlocklistEntry.cs @@ -6,11 +6,9 @@ namespace BirthdayBot.Data; [Table("banned_users")] public class BlocklistEntry { [Key] - [Column("guild_id")] - public long GuildId { get; set; } + public ulong GuildId { get; set; } [Key] - [Column("user_id")] - public long UserId { get; set; } + public ulong UserId { get; set; } [ForeignKey(nameof(GuildConfig.GuildId))] [InverseProperty(nameof(GuildConfig.BlockedUsers))] diff --git a/Data/Extensions.cs b/Data/Extensions.cs index 1b48e66..ace30ac 100644 --- a/Data/Extensions.cs +++ b/Data/Extensions.cs @@ -6,14 +6,14 @@ internal static class Extensions { /// If it doesn't exist in the database, returns true. /// public static GuildConfig GetConfigOrNew(this SocketGuild guild, BotDatabaseContext db) - => db.GuildConfigurations.Where(g => g.GuildId == (long)guild.Id).FirstOrDefault() - ?? new GuildConfig() { IsNew = true, GuildId = (long)guild.Id }; + => db.GuildConfigurations.Where(g => g.GuildId == guild.Id).FirstOrDefault() + ?? new GuildConfig() { IsNew = true, GuildId = guild.Id }; /// /// Gets the corresponding for this user in this guild, or a new one if one does not exist. /// If it doesn't exist in the database, returns true. /// public static UserEntry GetUserEntryOrNew(this SocketGuildUser user, BotDatabaseContext db) - => db.UserEntries.Where(u => u.GuildId == (long)user.Guild.Id && u.UserId == (long)user.Id).FirstOrDefault() - ?? new UserEntry() { IsNew = true, GuildId = (long)user.Guild.Id, UserId = (long)user.Id }; + => db.UserEntries.Where(u => u.GuildId == user.Guild.Id && u.UserId == user.Id).FirstOrDefault() + ?? new UserEntry() { IsNew = true, GuildId = user.Guild.Id, UserId = user.Id }; } \ No newline at end of file diff --git a/Data/GuildConfig.cs b/Data/GuildConfig.cs index df66398..2b3c009 100644 --- a/Data/GuildConfig.cs +++ b/Data/GuildConfig.cs @@ -10,25 +10,27 @@ public class GuildConfig { } [Key] - [Column("guild_id")] - public long GuildId { get; set; } + public ulong GuildId { get; set; } + [Column("role_id")] - public long? BirthdayRole { get; set; } + public ulong? BirthdayRole { get; set; } + [Column("channel_announce_id")] - public long? AnnouncementChannel { get; set; } + public ulong? AnnouncementChannel { get; set; } + [Column("time_zone")] public string? GuildTimeZone { get; set; } - [Column("moderated")] + public bool Moderated { get; set; } - [Column("moderator_role")] - public long? ModeratorRole { get; set; } - [Column("announce_message")] + + public ulong? ModeratorRole { get; set; } + public string? AnnounceMessage { get; set; } - [Column("announce_message_pl")] + public string? AnnounceMessagePl { get; set; } - [Column("announce_ping")] + public bool AnnouncePing { get; set; } - [Column("last_seen")] + public DateTimeOffset LastSeen { get; set; } [InverseProperty(nameof(BlocklistEntry.Guild))] diff --git a/Data/Migrations/20221123062847_LongToUlong.Designer.cs b/Data/Migrations/20221123062847_LongToUlong.Designer.cs new file mode 100644 index 0000000..ea9e8c0 --- /dev/null +++ b/Data/Migrations/20221123062847_LongToUlong.Designer.cs @@ -0,0 +1,161 @@ +// +using System; +using BirthdayBot.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace BirthdayBot.Data.Migrations +{ + [DbContext(typeof(BotDatabaseContext))] + [Migration("20221123062847_LongToUlong")] + partial class LongToUlong + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.0") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("BirthdayBot.Data.BlocklistEntry", b => + { + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guild_id"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("user_id"); + + b.HasKey("GuildId", "UserId") + .HasName("banned_users_pkey"); + + b.ToTable("banned_users", (string)null); + }); + + modelBuilder.Entity("BirthdayBot.Data.GuildConfig", b => + { + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guild_id"); + + b.Property("AnnounceMessage") + .HasColumnType("text") + .HasColumnName("announce_message"); + + b.Property("AnnounceMessagePl") + .HasColumnType("text") + .HasColumnName("announce_message_pl"); + + b.Property("AnnouncePing") + .HasColumnType("boolean") + .HasColumnName("announce_ping"); + + b.Property("AnnouncementChannel") + .HasColumnType("numeric(20,0)") + .HasColumnName("channel_announce_id"); + + b.Property("BirthdayRole") + .HasColumnType("numeric(20,0)") + .HasColumnName("role_id"); + + b.Property("GuildTimeZone") + .HasColumnType("text") + .HasColumnName("time_zone"); + + b.Property("LastSeen") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen") + .HasDefaultValueSql("now()"); + + b.Property("Moderated") + .HasColumnType("boolean") + .HasColumnName("moderated"); + + b.Property("ModeratorRole") + .HasColumnType("numeric(20,0)") + .HasColumnName("moderator_role"); + + b.HasKey("GuildId") + .HasName("settings_pkey"); + + b.ToTable("settings", (string)null); + }); + + modelBuilder.Entity("BirthdayBot.Data.UserEntry", b => + { + b.Property("GuildId") + .HasColumnType("numeric(20,0)") + .HasColumnName("guild_id"); + + b.Property("UserId") + .HasColumnType("numeric(20,0)") + .HasColumnName("user_id"); + + b.Property("BirthDay") + .HasColumnType("integer") + .HasColumnName("birth_day"); + + b.Property("BirthMonth") + .HasColumnType("integer") + .HasColumnName("birth_month"); + + b.Property("LastSeen") + .ValueGeneratedOnAdd() + .HasColumnType("timestamp with time zone") + .HasColumnName("last_seen") + .HasDefaultValueSql("now()"); + + b.Property("TimeZone") + .HasColumnType("text") + .HasColumnName("time_zone"); + + b.HasKey("GuildId", "UserId") + .HasName("user_birthdays_pkey"); + + b.ToTable("user_birthdays", (string)null); + }); + + modelBuilder.Entity("BirthdayBot.Data.BlocklistEntry", b => + { + b.HasOne("BirthdayBot.Data.GuildConfig", "Guild") + .WithMany("BlockedUsers") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("banned_users_guild_id_fkey"); + + b.Navigation("Guild"); + }); + + modelBuilder.Entity("BirthdayBot.Data.UserEntry", b => + { + b.HasOne("BirthdayBot.Data.GuildConfig", "Guild") + .WithMany("UserEntries") + .HasForeignKey("GuildId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("user_birthdays_guild_id_fkey"); + + b.Navigation("Guild"); + }); + + modelBuilder.Entity("BirthdayBot.Data.GuildConfig", b => + { + b.Navigation("BlockedUsers"); + + b.Navigation("UserEntries"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Data/Migrations/20221123062847_LongToUlong.cs b/Data/Migrations/20221123062847_LongToUlong.cs new file mode 100644 index 0000000..cf6e301 --- /dev/null +++ b/Data/Migrations/20221123062847_LongToUlong.cs @@ -0,0 +1,177 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace BirthdayBot.Data.Migrations +{ + /// + public partial class LongToUlong : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // NOTE: manually edited - must drop and re-add foreign key due to altered types + migrationBuilder.DropForeignKey( + name: "user_birthdays_guild_id_fkey", + table: "user_birthdays"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "user_birthdays", + type: "numeric(20,0)", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.AlterColumn( + name: "guild_id", + table: "user_birthdays", + type: "numeric(20,0)", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.AlterColumn( + name: "role_id", + table: "settings", + type: "numeric(20,0)", + nullable: true, + oldClrType: typeof(long), + oldType: "bigint", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "moderator_role", + table: "settings", + type: "numeric(20,0)", + nullable: true, + oldClrType: typeof(long), + oldType: "bigint", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "channel_announce_id", + table: "settings", + type: "numeric(20,0)", + nullable: true, + oldClrType: typeof(long), + oldType: "bigint", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "guild_id", + table: "settings", + type: "numeric(20,0)", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "banned_users", + type: "numeric(20,0)", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.AlterColumn( + name: "guild_id", + table: "banned_users", + type: "numeric(20,0)", + nullable: false, + oldClrType: typeof(long), + oldType: "bigint"); + + migrationBuilder.AddForeignKey( + name: "user_birthdays_guild_id_fkey", + table: "user_birthdays", + column: "guild_id", + principalTable: "settings", + principalColumn: "guild_id", + onDelete: ReferentialAction.Cascade); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "user_birthdays_guild_id_fkey", + table: "user_birthdays"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "user_birthdays", + type: "bigint", + nullable: false, + oldClrType: typeof(decimal), + oldType: "numeric(20,0)"); + + migrationBuilder.AlterColumn( + name: "guild_id", + table: "user_birthdays", + type: "bigint", + nullable: false, + oldClrType: typeof(decimal), + oldType: "numeric(20,0)"); + + migrationBuilder.AlterColumn( + name: "role_id", + table: "settings", + type: "bigint", + nullable: true, + oldClrType: typeof(decimal), + oldType: "numeric(20,0)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "moderator_role", + table: "settings", + type: "bigint", + nullable: true, + oldClrType: typeof(decimal), + oldType: "numeric(20,0)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "channel_announce_id", + table: "settings", + type: "bigint", + nullable: true, + oldClrType: typeof(decimal), + oldType: "numeric(20,0)", + oldNullable: true); + + migrationBuilder.AlterColumn( + name: "guild_id", + table: "settings", + type: "bigint", + nullable: false, + oldClrType: typeof(decimal), + oldType: "numeric(20,0)"); + + migrationBuilder.AlterColumn( + name: "user_id", + table: "banned_users", + type: "bigint", + nullable: false, + oldClrType: typeof(decimal), + oldType: "numeric(20,0)"); + + migrationBuilder.AlterColumn( + name: "guild_id", + table: "banned_users", + type: "bigint", + nullable: false, + oldClrType: typeof(decimal), + oldType: "numeric(20,0)"); + + migrationBuilder.AddForeignKey( + name: "user_birthdays_guild_id_fkey", + table: "user_birthdays", + column: "guild_id", + principalTable: "settings", + principalColumn: "guild_id", + onDelete: ReferentialAction.Cascade); + } + } +} diff --git a/Data/Migrations/BotDatabaseContextModelSnapshot.cs b/Data/Migrations/BotDatabaseContextModelSnapshot.cs index 837075b..98f9f26 100644 --- a/Data/Migrations/BotDatabaseContextModelSnapshot.cs +++ b/Data/Migrations/BotDatabaseContextModelSnapshot.cs @@ -17,19 +17,19 @@ namespace BirthdayBot.Data.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "6.0.3") + .HasAnnotation("ProductVersion", "7.0.0") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); modelBuilder.Entity("BirthdayBot.Data.BlocklistEntry", b => { - b.Property("GuildId") - .HasColumnType("bigint") + b.Property("GuildId") + .HasColumnType("numeric(20,0)") .HasColumnName("guild_id"); - b.Property("UserId") - .HasColumnType("bigint") + b.Property("UserId") + .HasColumnType("numeric(20,0)") .HasColumnName("user_id"); b.HasKey("GuildId", "UserId") @@ -40,8 +40,8 @@ namespace BirthdayBot.Data.Migrations modelBuilder.Entity("BirthdayBot.Data.GuildConfig", b => { - b.Property("GuildId") - .HasColumnType("bigint") + b.Property("GuildId") + .HasColumnType("numeric(20,0)") .HasColumnName("guild_id"); b.Property("AnnounceMessage") @@ -56,10 +56,18 @@ namespace BirthdayBot.Data.Migrations .HasColumnType("boolean") .HasColumnName("announce_ping"); - b.Property("ChannelAnnounceId") - .HasColumnType("bigint") + b.Property("AnnouncementChannel") + .HasColumnType("numeric(20,0)") .HasColumnName("channel_announce_id"); + b.Property("BirthdayRole") + .HasColumnType("numeric(20,0)") + .HasColumnName("role_id"); + + b.Property("GuildTimeZone") + .HasColumnType("text") + .HasColumnName("time_zone"); + b.Property("LastSeen") .ValueGeneratedOnAdd() .HasColumnType("timestamp with time zone") @@ -70,18 +78,10 @@ namespace BirthdayBot.Data.Migrations .HasColumnType("boolean") .HasColumnName("moderated"); - b.Property("ModeratorRole") - .HasColumnType("bigint") + b.Property("ModeratorRole") + .HasColumnType("numeric(20,0)") .HasColumnName("moderator_role"); - b.Property("RoleId") - .HasColumnType("bigint") - .HasColumnName("role_id"); - - b.Property("TimeZone") - .HasColumnType("text") - .HasColumnName("time_zone"); - b.HasKey("GuildId") .HasName("settings_pkey"); @@ -90,12 +90,12 @@ namespace BirthdayBot.Data.Migrations modelBuilder.Entity("BirthdayBot.Data.UserEntry", b => { - b.Property("GuildId") - .HasColumnType("bigint") + b.Property("GuildId") + .HasColumnType("numeric(20,0)") .HasColumnName("guild_id"); - b.Property("UserId") - .HasColumnType("bigint") + b.Property("UserId") + .HasColumnType("numeric(20,0)") .HasColumnName("user_id"); b.Property("BirthDay") diff --git a/Data/UserEntry.cs b/Data/UserEntry.cs index dc2e74f..49dc58e 100644 --- a/Data/UserEntry.cs +++ b/Data/UserEntry.cs @@ -6,18 +6,16 @@ namespace BirthdayBot.Data; [Table("user_birthdays")] public class UserEntry { [Key] - [Column("guild_id")] - public long GuildId { get; set; } + public ulong GuildId { get; set; } [Key] - [Column("user_id")] - public long UserId { get; set; } - [Column("birth_month")] + public ulong UserId { get; set; } + public int BirthMonth { get; set; } - [Column("birth_day")] + public int BirthDay { get; set; } - [Column("time_zone")] + public string? TimeZone { get; set; } - [Column("last_seen")] + public DateTimeOffset LastSeen { get; set; } [ForeignKey(nameof(GuildConfig.GuildId))]