diff --git a/ApplicationCommands/BirthdayModule.cs b/ApplicationCommands/BirthdayModule.cs index 76e0f9f..86d7906 100644 --- a/ApplicationCommands/BirthdayModule.cs +++ b/ApplicationCommands/BirthdayModule.cs @@ -46,7 +46,7 @@ public class BirthdayModule : BotModuleBase { if (user.IsNew) db.UserEntries.Add(user); user.BirthMonth = inmonth; user.BirthDay = inday; - user.TimeZone = inzone; + user.TimeZone = inzone ?? user.TimeZone; try { await db.SaveChangesAsync(); } catch (Microsoft.EntityFrameworkCore.DbUpdateException e) @@ -221,10 +221,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 5249827..5a87923 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.ChannelAnnounceId = (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.RoleId = (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(); var 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(); @@ -201,7 +201,7 @@ public class ConfigModule : BotModuleBase { result.AppendLine($"Server ID: `{guild.Id}` | Bot shard ID: `{Shard.ShardId:00}`"); result.AppendLine($"Number of registered birthdays: `{ guildconf.UserEntries.Count }`"); - result.AppendLine($"Server time zone: `{ (guildconf.TimeZone ?? "Not set - using UTC") }`"); + result.AppendLine($"Server time zone: `{ (guildconf.GuildTimeZone ?? "Not set - using UTC") }`"); result.AppendLine(); var hasMembers = Common.HasMostMembersDownloaded(guild); @@ -211,7 +211,7 @@ public class ConfigModule : BotModuleBase { result.Append(DoTestFor("Birthday processing", delegate { if (!hasMembers) return false; if (guildconf.IsNew) return false; - bdayCount = BackgroundServices.BirthdayRoleUpdate.GetGuildCurrentBirthdays(guildconf.UserEntries, guildconf.TimeZone).Count; + bdayCount = BackgroundServices.BirthdayRoleUpdate.GetGuildCurrentBirthdays(guildconf.UserEntries, guildconf.GuildTimeZone).Count; return true; })); if (!hasMembers) result.AppendLine(" - Previous step failed."); @@ -221,12 +221,12 @@ public class ConfigModule : BotModuleBase { result.AppendLine(DoTestFor("Birthday role set with `/config role set-birthday-role`", delegate { if (guildconf.IsNew) return false; - SocketRole? role = guild.GetRole((ulong)(guildconf.RoleId ?? 0)); + SocketRole? role = guild.GetRole((ulong)(guildconf.BirthdayRole ?? 0)); return role != null; })); result.AppendLine(DoTestFor("Birthday role can be managed by bot", delegate { if (guildconf.IsNew) return false; - SocketRole? role = guild.GetRole((ulong)(guildconf.RoleId ?? 0)); + SocketRole? role = guild.GetRole((ulong)(guildconf.BirthdayRole ?? 0)); if (role == null) return false; return guild.CurrentUser.GuildPermissions.ManageRoles && role.Position < guild.CurrentUser.Hierarchy; })); @@ -235,7 +235,7 @@ public class ConfigModule : BotModuleBase { SocketTextChannel? announcech = null; result.AppendLine(DoTestFor("(Optional) Announcement channel set with `bb.config channel`", delegate { if (guildconf.IsNew) return false; - announcech = guild.GetTextChannel((ulong)(guildconf.ChannelAnnounceId ?? 0)); + announcech = guild.GetTextChannel((ulong)(guildconf.AnnouncementChannel ?? 0)); return announcech != null; })); var disp = announcech == null ? "announcement channel" : $"<#{announcech.Id}>"; @@ -275,7 +275,7 @@ public class ConfigModule : BotModuleBase { const string Response = ":white_check_mark: The server's time zone has been "; if (zone == null) { - await DoDatabaseUpdate(Context, s => s.TimeZone = null); + await DoDatabaseUpdate(Context, s => s.GuildTimeZone = null); await RespondAsync(Response + "unset.").ConfigureAwait(false); } else { string parsedZone; @@ -286,7 +286,7 @@ public class ConfigModule : BotModuleBase { return; } - await DoDatabaseUpdate(Context, s => s.TimeZone = parsedZone); + await DoDatabaseUpdate(Context, s => s.GuildTimeZone = parsedZone); await RespondAsync(Response + $"set to **{parsedZone}**.").ConfigureAwait(false); } } diff --git a/ApplicationCommands/Preconditions/EnforceBlocking.cs b/ApplicationCommands/Preconditions/EnforceBlocking.cs index d6f45e4..b7d9df9 100644 --- a/ApplicationCommands/Preconditions/EnforceBlocking.cs +++ b/ApplicationCommands/Preconditions/EnforceBlocking.cs @@ -23,8 +23,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()); @@ -33,7 +33,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 c7e8225..74aaf3b 100644 --- a/ApplicationCommands/Preconditions/RequireBotModerator.cs +++ b/ApplicationCommands/Preconditions/RequireBotModerator.cs @@ -22,8 +22,8 @@ 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) - .Select(g => g.RoleId).FirstOrDefault(); + .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 cdc59d0..dffcd6f 100644 --- a/BackgroundServices/AutoUserDownload.cs +++ b/BackgroundServices/AutoUserDownload.cs @@ -1,4 +1,5 @@ using BirthdayBot.Data; +using Microsoft.EntityFrameworkCore; namespace BirthdayBot.BackgroundServices; /// @@ -8,13 +9,22 @@ 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(); + var incompleteCaches = ShardInstance.DiscordClient.Guilds.Where(g => !g.HasAllMembers).Select(g => 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(); - + 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. @@ -27,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)."); } } diff --git a/BackgroundServices/BirthdayRoleUpdate.cs b/BackgroundServices/BirthdayRoleUpdate.cs index d907932..25f621b 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) { @@ -46,13 +46,13 @@ class BirthdayRoleUpdate : BackgroundService { try { // Verify that role settings and permissions are usable - SocketRole? role = guild.GetRole((ulong)(settings.RoleId ?? 0)); + SocketRole? role = guild.GetRole((ulong)(settings.BirthdayRole ?? 0)); if (role == null || !guild.CurrentUser.GuildPermissions.ManageRoles || role.Position >= guild.CurrentUser.Hierarchy) continue; if (role.IsEveryone || role.IsManaged) { // Invalid role was configured. Clear the setting and quit. - settings.RoleId = null; + settings.BirthdayRole = null; db.Update(settings); await db.SaveChangesAsync(CancellationToken.None); continue; @@ -60,7 +60,7 @@ class BirthdayRoleUpdate : BackgroundService { // Load up user configs and begin processing birthdays await db.Entry(settings).Collection(t => t.UserEntries).LoadAsync(CancellationToken.None); - var birthdays = GetGuildCurrentBirthdays(settings.UserEntries, settings.TimeZone); + var birthdays = GetGuildCurrentBirthdays(settings.UserEntries, settings.GuildTimeZone); // Add or remove roles as appropriate var announcementList = await UpdateGuildBirthdayRoles(guild, role, birthdays); @@ -144,7 +144,7 @@ class BirthdayRoleUpdate : BackgroundService { /// Attempts to send an announcement message. /// internal static async Task AnnounceBirthdaysAsync(GuildConfig settings, SocketGuild g, IEnumerable names) { - var c = g.GetTextChannel((ulong)(settings.ChannelAnnounceId ?? 0)); + var c = g.GetTextChannel((ulong)(settings.AnnouncementChannel ?? 0)); if (c == null) return; if (!c.Guild.CurrentUser.GetPermissions(c).SendMessages) return; diff --git a/BackgroundServices/DataRetention.cs b/BackgroundServices/DataRetention.cs index 2caa3e8..6f0a0e6 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 => 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 => u.Id).ToList(); + updatedUsers += await db.UserEntries + .Where(gu => gu.GuildId == 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 4359c69..d8150f9 100644 --- a/BirthdayBot.csproj +++ b/BirthdayBot.csproj @@ -5,7 +5,7 @@ net6.0 enable enable - 3.4.4 + 3.4.6 NoiTheCat @@ -22,17 +22,17 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + - - - + + + diff --git a/Configuration.cs b/Configuration.cs index d54be45..94623a5 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -70,7 +70,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/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/BotDatabaseContext.cs b/Data/BotDatabaseContext.cs index d0f458e..cd46f7e 100644 --- a/Data/BotDatabaseContext.cs +++ b/Data/BotDatabaseContext.cs @@ -13,8 +13,7 @@ public class BotDatabaseContext : DbContext { Database = conf.SqlDatabase, Username = conf.SqlUsername, Password = conf.SqlPassword, - ApplicationName = conf.SqlApplicationName, - MaxPoolSize = Math.Max((int)Math.Ceiling(conf.ShardAmount * 2 * 0.6), 8) + ApplicationName = conf.SqlApplicationName }.ToString(); } diff --git a/Data/Extensions.cs b/Data/Extensions.cs index 0014110..01ad926 100644 --- a/Data/Extensions.cs +++ b/Data/Extensions.cs @@ -5,14 +5,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 284e90b..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? RoleId { get; set; } + public ulong? BirthdayRole { get; set; } + [Column("channel_announce_id")] - public long? ChannelAnnounceId { get; set; } + public ulong? AnnouncementChannel { get; set; } + [Column("time_zone")] - public string? TimeZone { get; set; } - [Column("moderated")] + public string? GuildTimeZone { get; set; } + 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 e388d84..425cb66 100644 --- a/Data/UserEntry.cs +++ b/Data/UserEntry.cs @@ -5,18 +5,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))] diff --git a/ShardManager.cs b/ShardManager.cs index fdb229f..67944a7 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; @@ -18,7 +17,7 @@ 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;